goose/
goose.rs

1//! Helpers and objects for building Goose load tests.
2//!
3//! Goose manages load tests with a series of objects:
4//!
5//! - [`Scenario`] each user is assigned a scenario, which is a collection of transactions.
6//! - [`Transaction`] transactions define one or more web requests and are assigned to scenarios.
7//! - [`GooseUser`] a user state responsible for repeatedly running all transactions in the assigned scenario.
8//! - [`GooseRequestMetric`] optional metrics collected for each URL/method pair.
9//!
10//! ## Creating Scenarios
11//!
12//! A [`Scenario`](./struct.Scenario.html) is created by passing in a `&str` name to the `new` function, for example:
13//!
14//! ```rust
15//! use goose::prelude::*;
16//!
17//! let mut loadtest_transactions = scenario!("LoadtestTransactions");
18//! ```
19//!
20//! ### Scenario Weight
21//!
22//! A weight can be applied to a scenario, controlling how often it is assigned to
23//! [`GooseUser`](../goose/struct.GooseUser.html) threads. The larger the integer value
24//! of weight, the more the scenario will be assigned to user threads. In the following
25//! example, `FooTransactions` will be assigned to users twice as often as `Bar`
26//! Transactions. We could have just added a weight of `2` to `FooTransactions` and left
27//! the default weight of `1` assigned to `BarTransactions` for the same weighting:
28//!
29//! ```rust
30//! use goose::prelude::*;
31//!
32//! #[tokio::main]
33//! async fn main() -> Result<(), GooseError> {
34//!     let mut foo_transactions = scenario!("FooTransactions").set_weight(10)?;
35//!     let mut bar_transactions = scenario!("BarTransactions").set_weight(5)?;
36//!
37//!     Ok(())
38//! }
39//! ```
40//!
41//! ### Scenario Host
42//!
43//! A default host can be assigned to a scenario, which will be used only if the `--host`
44//! CLI option is not set at run-time. For example, this can configure your load test to
45//! run against your local development environment by default, allowing the `--host` option
46//! to override host when you want to load test production. You can also assign different
47//! hosts to different scenario if this is desirable:
48//!
49//! ```rust
50//! use goose::prelude::*;
51//!
52//! let mut foo_transactions = scenario!("FooTransactions").set_host("http://www.local");
53//! let mut bar_transactions = scenario!("BarTransactions").set_host("http://www2.local");
54//! ```
55//!
56//! ### Scenario Wait Time
57//!
58//! Wait time is specified as a low-high Duration range. Each time a transaction completes in the
59//! scenario, the user will pause for a random number of milliseconds inclusively between
60//! the low and high wait times. In the following example, users loading `foo` transactions will
61//! sleep 0 to 2.5 seconds after each transaction completes, and users loading `bar` transactions will
62//! always sleep 5 seconds after each transaction completes.
63//!
64//! ```rust
65//! use goose::prelude::*;
66//! use std::time::Duration;
67//!
68//! let mut foo_transactions = scenario!("FooTransactions").set_wait_time(Duration::from_secs(0), Duration::from_millis(2500)).unwrap();
69//! let mut bar_transactions = scenario!("BarTransactions").set_wait_time(Duration::from_secs(5), Duration::from_secs(5)).unwrap();
70//! ```
71//! ## Creating Transactions
72//!
73//! A [`Transaction`](./struct.Transaction.html) must include a pointer to a function which
74//! will be executed each time the transaction is run.
75//!
76//! ```rust
77//! use goose::prelude::*;
78//!
79//! let mut a_transaction = transaction!(transaction_function);
80//!
81//! /// A very simple transaction that loads the front page.
82//! async fn transaction_function(user: &mut GooseUser) -> TransactionResult {
83//!     let _goose = user.get("").await?;
84//!
85//!     Ok(())
86//! }
87//! ```
88//!
89//! ### Transaction Name
90//!
91//! A name can be assigned to a transaction, and will be displayed in metrics about all requests
92//! made by the transaction.
93//!
94//! ```rust
95//! use goose::prelude::*;
96//!
97//! let mut a_transaction = transaction!(transaction_function).set_name("a");
98//!
99//! /// A very simple transaction that loads the front page.
100//! async fn transaction_function(user: &mut GooseUser) -> TransactionResult {
101//!     let _goose = user.get("").await?;
102//!
103//!     Ok(())
104//! }
105//! ```
106//!
107//! ### Transaction Weight
108//!
109//! Individual transactions can be assigned a weight, controlling how often the transaction runs. The
110//! larger the value of weight, the more it will run. In the following example, `a_transaction`
111//! runs 3 times as often as `b_transaction`:
112//!
113//! ```rust
114//! use goose::prelude::*;
115//!
116//! #[tokio::main]
117//! async fn main() -> Result<(), GooseError> {
118//!     let mut a_transaction = transaction!(a_transaction_function).set_weight(9)?;
119//!     let mut b_transaction = transaction!(b_transaction_function).set_weight(3)?;
120//!
121//!     Ok(())
122//! }
123//!
124//! /// A very simple transaction that loads the "a" page.
125//! async fn a_transaction_function(user: &mut GooseUser) -> TransactionResult {
126//!     let _goose = user.get("a/").await?;
127//!
128//!     Ok(())
129//! }
130//!
131//! /// Another very simple transaction that loads the "b" page.
132//! async fn b_transaction_function(user: &mut GooseUser) -> TransactionResult {
133//!     let _goose = user.get("b/").await?;
134//!
135//!     Ok(())
136//! }
137//! ```
138//!
139//! ### Transaction Sequence
140//!
141//! Transactions can also be configured to run in a sequence. For example, a transaction with a
142//! sequence value of `1` will always run before a transaction with a sequence value of `2`. Weight can
143//! be applied to sequenced transactions, so for example a transaction with a weight of `2` and a sequence
144//! of `1` will run two times before a transaction with a sequence of `2`. Scenarios can contain
145//! transactions with sequence values and without sequence values, and in this case all transactions with
146//! a sequence value will run before transactions without a sequence value. In the following example,
147//! `a_transaction` runs before `b_transaction`, which runs before `c_transaction`:
148//!
149//! ```rust
150//! use goose::prelude::*;
151//!
152//! let mut a_transaction = transaction!(a_transaction_function).set_sequence(1);
153//! let mut b_transaction = transaction!(b_transaction_function).set_sequence(2);
154//! let mut c_transaction = transaction!(c_transaction_function);
155//!
156//! /// A very simple transaction that loads the "a" page.
157//! async fn a_transaction_function(user: &mut GooseUser) -> TransactionResult {
158//!     let _goose = user.get("a/").await?;
159//!
160//!     Ok(())
161//! }
162//!
163//! /// Another very simple transaction that loads the "b" page.
164//! async fn b_transaction_function(user: &mut GooseUser) -> TransactionResult {
165//!     let _goose = user.get("b/").await?;
166//!
167//!     Ok(())
168//! }
169//!
170//! /// Another very simple transaction that loads the "c" page.
171//! async fn c_transaction_function(user: &mut GooseUser) -> TransactionResult {
172//!     let _goose = user.get("c/").await?;
173//!
174//!     Ok(())
175//! }
176//! ```
177//!
178//! ### Transaction On Start
179//!
180//! Transactions can be flagged to only run when a user first starts. This can be useful if you'd
181//! like your load test to use a logged-in user. It is possible to assign sequences and weights
182//! to [`on_start`](./struct.Transaction.html#method.set_on_start) functions if you want to have
183//! multiple transactions run in a specific order at start time, and/or the transactions to run multiple times.
184//! A transaction can be flagged to run both on start and on stop.
185//!
186//! ```rust
187//! use goose::prelude::*;
188//!
189//! let mut a_transaction = transaction!(a_transaction_function).set_sequence(1).set_on_start();
190//!
191//! /// A very simple transaction that loads the "a" page.
192//! async fn a_transaction_function(user: &mut GooseUser) -> TransactionResult {
193//!     let _goose = user.get("a/").await?;
194//!
195//!     Ok(())
196//! }
197//! ```
198//!
199//! ### Transaction On Stop
200//!
201//! Transactions can be flagged to only run when a user stops. This can be useful if you'd like your
202//! load test to simulate a user logging out when it finishes. It is possible to assign sequences
203//! and weights to [`on_stop`](./struct.Transaction.html#method.set_on_stop) functions if you want to
204//! have multiple transactions run in a specific order at stop time, and/or the transactions to run multiple
205//! times. A transaction can be flagged to run both on start and on stop.
206//!
207//! ```rust
208//! use goose::prelude::*;
209//!
210//! let mut b_transaction = transaction!(b_transaction_function).set_sequence(2).set_on_stop();
211//!
212//! /// Another very simple transaction that loads the "b" page.
213//! async fn b_transaction_function(user: &mut GooseUser) -> TransactionResult {
214//!     let _goose = user.get("b/").await?;
215//!
216//!     Ok(())
217//! }
218//! ```
219//!
220//! ## Controlling User
221//!
222//! When Goose starts, it creates one or more [`GooseUser`](./struct.GooseUser.html)s,
223//! assigning a single [`Scenario`](./struct.Scenario.html) to each. This user is
224//! then used to generate load. Behind the scenes, Goose is leveraging the
225//! [`reqwest::client`](https://docs.rs/reqwest/*/reqwest/struct.Client.html)
226//! to load web pages, and Goose can therefor do anything [`reqwest`](https://docs.rs/reqwest/)
227//! can do.
228//!
229//! The most common request types are [`GET`](./struct.GooseUser.html#method.get) and
230//! [`POST`](./struct.GooseUser.html#method.post), but [`HEAD`](./struct.GooseUser.html#method.head),
231//! PUT, PATCH and [`DELETE`](./struct.GooseUser.html#method.delete) are also supported.
232//!
233//! ### GET
234//!
235//! A helper to make a `GET` request of a path and collect relevant metrics.
236//! Automatically prepends the correct host.
237//!
238//! ```rust
239//! use goose::prelude::*;
240//!
241//! let mut transaction = transaction!(get_function);
242//!
243//! /// A very simple transaction that makes a GET request.
244//! async fn get_function(user: &mut GooseUser) -> TransactionResult {
245//!     let _goose = user.get("path/to/foo/").await?;
246//!
247//!     Ok(())
248//! }
249//! ```
250//!
251//! The returned response is a [`reqwest::Response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)
252//! struct. You can use it as you would any Reqwest Response.
253//!
254//!
255//! ### POST
256//!
257//! A helper to make a `POST` request of a string value to the path and collect relevant
258//! metrics. Automatically prepends the correct host. The returned response is a
259//! [`reqwest::Response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)
260//!
261//! ```rust
262//! use goose::prelude::*;
263//!
264//! let mut transaction = transaction!(post_function);
265//!
266//! /// A very simple transaction that makes a POST request.
267//! async fn post_function(user: &mut GooseUser) -> TransactionResult {
268//!     let _goose = user.post("path/to/foo/", "string value to post").await?;
269//!
270//!     Ok(())
271//! }
272//! ```
273//!
274//! ## License
275//!
276//! Copyright 2020-2023 Jeremy Andrews
277//!
278//! Licensed under the Apache License, Version 2.0 (the "License");
279//! you may not use this file except in compliance with the License.
280//! You may obtain a copy of the License at
281//!
282//! <http://www.apache.org/licenses/LICENSE-2.0>
283//!
284//! Unless required by applicable law or agreed to in writing, software
285//! distributed under the License is distributed on an "AS IS" BASIS,
286//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
287//! See the License for the specific language governing permissions and
288//! limitations under the License.
289
290use downcast_rs::{impl_downcast, Downcast};
291use regex::Regex;
292use reqwest::{header, Client, ClientBuilder, Method, RequestBuilder, Response};
293use serde::{Deserialize, Serialize};
294use std::fmt::{Debug, Formatter};
295use std::hash::{Hash, Hasher};
296use std::sync::Arc;
297use std::time::Duration;
298use std::{fmt, str};
299use std::{future::Future, pin::Pin, time::Instant};
300use url::Url;
301
302use crate::logger::GooseLog;
303use crate::metrics::{
304    GooseCoordinatedOmissionMitigation, GooseMetric, GooseRawRequest, GooseRequestMetric,
305    TransactionDetail,
306};
307use crate::{GooseConfiguration, GooseError, WeightedTransactions};
308
309/// By default Goose sets the following User-Agent header when making requests.
310static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
311
312/// By default Goose times out requests after 60,000 milliseconds.
313static GOOSE_REQUEST_TIMEOUT: u64 = 60_000;
314
315/// `transaction!(foo)` expands to `Transaction::new(foo)`, but also does some boxing to work around a limitation in the compiler.
316#[macro_export]
317macro_rules! transaction {
318    ($transaction_func:ident) => {
319        Transaction::new(std::sync::Arc::new(move |s| {
320            std::boxed::Box::pin($transaction_func(s))
321        }))
322    };
323}
324
325/// `scenario!("foo")` expands to `Scenario::new("foo")`.
326#[macro_export]
327macro_rules! scenario {
328    ($name:tt) => {
329        Scenario::new($name)
330    };
331}
332
333/// Goose transactions return a result, which is empty on success, or contains a boxed
334/// [`TransactionError`](./enum.TransactionError.html) on error.
335pub type TransactionResult = Result<(), Box<TransactionError>>;
336
337/// An enumeration of all errors a [`Transaction`](./struct.Transaction.html) can return.
338#[derive(Debug)]
339pub enum TransactionError {
340    /// Wraps a [`reqwest::Error`](https://docs.rs/reqwest/*/reqwest/struct.Error.html).
341    Reqwest(reqwest::Error),
342    /// Wraps a [`url::ParseError`](https://docs.rs/url/*/url/enum.ParseError.html).
343    Url(url::ParseError),
344    /// The request failed.
345    RequestFailed {
346        /// The [`GooseRequestMetric`](./struct.GooseRequestMetric.html) that failed.
347        raw_request: GooseRequestMetric,
348    },
349    /// The request was canceled. This happens when the throttle is enabled and the load
350    /// test finishes.
351    RequestCanceled {
352        /// Wraps a [`flume::SendError`](https://docs.rs/flume/*/flume/struct.SendError.html),
353        /// a [`GooseRequestMetric`](./struct.GooseRequestMetric.html) has not yet been constructed.
354        source: flume::SendError<bool>,
355    },
356    /// There was an error sending the metrics for a request to the parent thread.
357    MetricsFailed {
358        /// Wraps a [`flume::SendError`](https://docs.rs/flume/*/flume/struct.SendError.html),
359        /// which contains the [`GooseMetric`](../metrics/enum.GooseMetric.html) that wasn't sent.
360        source: flume::SendError<GooseMetric>,
361    },
362    /// There was an error sending debug information to the logger thread.
363    LoggerFailed {
364        /// Wraps a [`flume::SendError`](https://docs.rs/flume/*/flume/struct.SendError.html),
365        /// which contains the [`GooseDebug`](./struct.GooseDebug.html) that wasn't sent.
366        source: flume::SendError<Option<GooseLog>>,
367    },
368    /// Attempted an unrecognized HTTP request method.
369    InvalidMethod {
370        /// The unrecognized HTTP request method.
371        method: Method,
372    },
373}
374/// Implement a helper to provide a text description of all possible types of errors.
375impl TransactionError {
376    fn describe(&self) -> &str {
377        match *self {
378            TransactionError::Reqwest(_) => "reqwest::Error",
379            TransactionError::Url(_) => "url::ParseError",
380            TransactionError::RequestFailed { .. } => "request failed",
381            TransactionError::RequestCanceled { .. } => {
382                "request canceled because throttled load test ended"
383            }
384            TransactionError::MetricsFailed { .. } => "failed to send metrics to parent thread",
385            TransactionError::LoggerFailed { .. } => "failed to send log message to logger thread",
386            TransactionError::InvalidMethod { .. } => "unrecognized HTTP request method",
387        }
388    }
389}
390
391/// Implement format trait to allow displaying errors.
392impl fmt::Display for TransactionError {
393    // Implement display of error with `{}` marker.
394    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
395        match *self {
396            TransactionError::Reqwest(ref source) => {
397                write!(f, "TransactionError: {} ({})", self.describe(), source)
398            }
399            TransactionError::Url(ref source) => {
400                write!(f, "TransactionError: {} ({})", self.describe(), source)
401            }
402            TransactionError::RequestCanceled { ref source } => {
403                write!(f, "TransactionError: {} ({})", self.describe(), source)
404            }
405            TransactionError::MetricsFailed { ref source } => {
406                write!(f, "TransactionError: {} ({})", self.describe(), source)
407            }
408            TransactionError::LoggerFailed { ref source } => {
409                write!(f, "TransactionError: {} ({})", self.describe(), source)
410            }
411            _ => write!(f, "TransactionError: {}", self.describe()),
412        }
413    }
414}
415
416// Define the lower level source of this error, if any.
417impl std::error::Error for TransactionError {
418    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
419        match *self {
420            TransactionError::Reqwest(ref source) => Some(source),
421            TransactionError::Url(ref source) => Some(source),
422            TransactionError::RequestCanceled { ref source } => Some(source),
423            TransactionError::MetricsFailed { ref source } => Some(source),
424            TransactionError::LoggerFailed { ref source } => Some(source),
425            _ => None,
426        }
427    }
428}
429
430/// Auto-convert Reqwest errors.
431impl From<reqwest::Error> for TransactionError {
432    fn from(err: reqwest::Error) -> TransactionError {
433        TransactionError::Reqwest(err)
434    }
435}
436
437/// Auto-convert Reqwest errors to boxed TransactionError.
438impl From<reqwest::Error> for Box<TransactionError> {
439    fn from(value: reqwest::Error) -> Self {
440        Box::new(TransactionError::Reqwest(value))
441    }
442}
443
444/// Auto-convert Url errors.
445impl From<url::ParseError> for TransactionError {
446    fn from(err: url::ParseError) -> TransactionError {
447        TransactionError::Url(err)
448    }
449}
450
451/// When the throttle is enabled and the load test ends, the throttle channel is
452/// shut down. This causes a
453/// [`flume::SendError`](https://docs.rs/flume/*/flume/struct.SendError.html),
454/// which gets automatically converted to `RequestCanceled`.
455/// [`RequestCanceled`](./enum.TransactionError.html#variant.RequestCanceled)
456impl From<flume::SendError<bool>> for TransactionError {
457    fn from(source: flume::SendError<bool>) -> TransactionError {
458        TransactionError::RequestCanceled { source }
459    }
460}
461
462/// Attempt to send metrics to the parent thread failed.
463impl From<flume::SendError<GooseMetric>> for TransactionError {
464    fn from(source: flume::SendError<GooseMetric>) -> TransactionError {
465        TransactionError::MetricsFailed { source }
466    }
467}
468
469/// Attempt to send logs to the logger thread failed.
470impl From<flume::SendError<Option<GooseLog>>> for TransactionError {
471    fn from(source: flume::SendError<Option<GooseLog>>) -> TransactionError {
472        TransactionError::LoggerFailed { source }
473    }
474}
475
476/// An individual scenario.
477#[derive(Clone, Hash)]
478pub struct Scenario {
479    /// The name of the scenario.
480    pub name: String,
481    /// Auto-generated machine name of the scenario.
482    pub machine_name: String,
483    /// An integer reflecting where this scenario lives in the internal
484    /// [`GooseAttack`](../struct.GooseAttack.html)`.scenarios` vector.
485    pub scenarios_index: usize,
486    /// An integer value that controls the frequency that this scenario will be assigned to a user.
487    pub weight: usize,
488    /// A [`Duration`](https://doc.rust-lang.org/std/time/struct.Duration.html) range defining the
489    /// minimum and maximum time a [`GooseUser`] should sleep after running a transaction.
490    pub transaction_wait: Option<(Duration, Duration)>,
491    /// A vector containing one copy of each [`Transaction`](./struct.Transaction.html) that will
492    /// run by users running this scenario.
493    pub transactions: Vec<Transaction>,
494    /// A fully scheduled and weighted vector of integers (pointing to
495    /// [`Transaction`](./struct.Transaction.html)s and [`Transaction`](./struct.Transaction.html) names.
496    pub weighted_transactions: WeightedTransactions,
497    /// A vector of vectors of integers, controlling the sequence and order
498    /// [`on_start`](./struct.Transaction.html#method.set_on_start)
499    /// [`Transaction`](./struct.Transaction.html)s are run when the user first starts.
500    pub weighted_on_start_transactions: WeightedTransactions,
501    /// A vector of vectors of integers, controlling the sequence and order
502    /// [`on_stop`](./struct.Transaction.html#method.set_on_stop)
503    /// [`Transaction`](./struct.Transaction.html)s are run when the user first starts.
504    pub weighted_on_stop_transactions: WeightedTransactions,
505    /// An optional default host to run this `Scenario` against.
506    pub host: Option<String>,
507}
508impl Scenario {
509    /// Creates a new [`Scenario`](./struct.Scenario.html). Once created, a
510    /// [`Transaction`](./struct.Transaction.html) must be assigned to it, and finally it must
511    /// be registered with the [`GooseAttack`](../struct.GooseAttack.html) object. The
512    /// returned object must be stored in a mutable value.
513    ///
514    /// # Example
515    /// ```rust
516    /// use goose::prelude::*;
517    ///
518    /// let mut example_transactions = scenario!("ExampleTransactions");
519    /// ```
520    pub fn new(name: &str) -> Self {
521        trace!("new scenario: name: {}", &name);
522        Scenario {
523            name: name.to_string(),
524            machine_name: Scenario::get_machine_name(name),
525            scenarios_index: usize::MAX,
526            weight: 1,
527            transaction_wait: None,
528            transactions: Vec::new(),
529            weighted_transactions: Vec::new(),
530            weighted_on_start_transactions: Vec::new(),
531            weighted_on_stop_transactions: Vec::new(),
532            host: None,
533        }
534    }
535
536    /// An internal helper to convert a Scenario name to a machine name which is only
537    /// alphanumerics.
538    fn get_machine_name(name: &str) -> String {
539        // Remove all non-alphanumeric characters.
540        let re = Regex::new("[^a-zA-Z0-9]+").unwrap();
541        let alphanumeric = re.replace_all(name, "");
542        // Convert to lower case.
543        alphanumeric.to_lowercase()
544    }
545
546    /// Registers a [`Transaction`](./struct.Transaction.html) with a
547    /// [`Scenario`](./struct.Scenario.html), where it is stored in the
548    /// [`Scenario`](./struct.Scenario.html)`.transactions` vector. The function
549    /// associated with the transaction will be run during the load test.
550    ///
551    /// # Example
552    /// ```rust
553    /// use goose::prelude::*;
554    ///
555    /// let mut example_transactions = scenario!("ExampleTransactions");
556    /// example_transactions.register_transaction(transaction!(a_transaction_function));
557    ///
558    /// /// A very simple transaction that loads the "a" page.
559    /// async fn a_transaction_function(user: &mut GooseUser) -> TransactionResult {
560    ///     let _goose = user.get("a/").await?;
561    ///
562    ///     Ok(())
563    /// }
564    /// ```
565    pub fn register_transaction(mut self, mut transaction: Transaction) -> Self {
566        trace!("{} register_transaction: {}", self.name, transaction.name);
567        transaction.transactions_index = self.transactions.len();
568        self.transactions.push(transaction);
569        self
570    }
571
572    /// Sets a weight on a scenario. The larger the value of weight, the more often the scenario will
573    /// be assigned to users. For example, if you have scenario foo with a weight of 3, and scenario
574    /// bar with a weight of 1, and you spin up a load test with 8 users, 6 of them will be running
575    /// the foo scenario, and 2 will be running the bar scenario.
576    ///
577    /// # Example
578    /// ```rust
579    /// use goose::prelude::*;
580    ///
581    /// #[tokio::main]
582    /// async fn main() -> Result<(), GooseError> {
583    ///     let mut example_transactions = scenario!("ExampleTransactions").set_weight(3)?;
584    ///
585    ///     Ok(())
586    /// }
587    /// ```
588    pub fn set_weight(mut self, weight: usize) -> Result<Self, GooseError> {
589        trace!("{} set_weight: {}", self.name, weight);
590        if weight == 0 {
591            return Err(GooseError::InvalidWeight {
592                weight,
593                detail: ("Weight must be set to at least 1.".to_string()),
594            });
595        }
596        self.weight = weight;
597
598        Ok(self)
599    }
600
601    /// Set a default host for the scenario. If no `--host` flag is set when running the load test, this
602    /// host will be pre-pended on all requests. For example, this can configure your load test to run
603    /// against your local development environment by default, and the `--host` option could be used to
604    /// override host when running the load test against production.
605    ///
606    /// # Example
607    /// ```rust
608    /// use goose::prelude::*;
609    ///
610    /// let mut example_transactions = scenario!("ExampleTransactions").set_host("http://10.1.1.42");
611    /// ```
612    pub fn set_host(mut self, host: &str) -> Self {
613        trace!("{} set_host: {}", self.name, host);
614        // Host validation happens in main() at startup.
615        self.host = Some(host.to_string());
616        self
617    }
618
619    /// Configure a senario to to pause after running each transaction. The length of the pause will be randomly
620    /// selected from `min_wait` to `max_wait` inclusively.
621    ///
622    /// # Example
623    /// ```rust
624    /// use goose::prelude::*;
625    /// use std::time::Duration;
626    ///
627    /// #[tokio::main]
628    /// async fn main() -> Result<(), GooseError> {
629    ///     scenario!("ExampleTransactions").set_wait_time(Duration::from_secs(0), Duration::from_secs(1))?;
630    ///
631    ///     Ok(())
632    /// }
633    /// ```
634    pub fn set_wait_time(
635        mut self,
636        min_wait: Duration,
637        max_wait: Duration,
638    ) -> Result<Self, GooseError> {
639        trace!(
640            "{} set_wait time: min: {:?} max: {:?}",
641            self.name,
642            min_wait,
643            max_wait
644        );
645        if min_wait.as_millis() > max_wait.as_millis() {
646            return Err(GooseError::InvalidWaitTime {
647                min_wait,
648                max_wait,
649                detail:
650                    "The min_wait option can not be set to a larger value than the max_wait option."
651                        .to_string(),
652            });
653        }
654        self.transaction_wait = Some((min_wait, max_wait));
655
656        Ok(self)
657    }
658}
659
660/// Commands sent from the parent thread to the user threads, and from the manager to the
661/// worker processes.
662#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
663pub enum GooseUserCommand {
664    /// Tell worker process to pause load test.
665    Wait,
666    /// Tell worker process to start load test.
667    Run,
668    /// Tell user thread or worker process to exit.
669    Exit,
670}
671
672/// Supported HTTP methods.
673#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)]
674pub enum GooseMethod {
675    Delete,
676    Get,
677    Head,
678    Patch,
679    Post,
680    Put,
681}
682/// Display method in upper case.
683impl fmt::Display for GooseMethod {
684    // Implement display of `GooseMethod` with `{}` marker.
685    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
686        match self {
687            GooseMethod::Delete => write!(f, "DELETE"),
688            GooseMethod::Get => write!(f, "GET"),
689            GooseMethod::Head => write!(f, "HEAD"),
690            GooseMethod::Patch => write!(f, "PATCH"),
691            GooseMethod::Post => write!(f, "POST"),
692            GooseMethod::Put => write!(f, "PUT"),
693        }
694    }
695}
696
697/// Convert [`http::method::Method`](https://docs.rs/http/*/http/method/struct.Method.html)
698/// to [`GooseMethod`](./enum.GooseMethod.html).
699pub fn goose_method_from_method(method: Method) -> Result<GooseMethod, Box<TransactionError>> {
700    Ok(match method {
701        Method::DELETE => GooseMethod::Delete,
702        Method::GET => GooseMethod::Get,
703        Method::HEAD => GooseMethod::Head,
704        Method::PATCH => GooseMethod::Patch,
705        Method::POST => GooseMethod::Post,
706        Method::PUT => GooseMethod::Put,
707        _ => {
708            return Err(Box::new(TransactionError::InvalidMethod { method }));
709        }
710    })
711}
712
713/// The response to a [`GooseRequestMetric`].
714#[derive(Debug)]
715pub struct GooseResponse {
716    /// The request that this is a response to.
717    pub request: GooseRequestMetric,
718    /// The response.
719    pub response: Result<Response, reqwest::Error>,
720}
721impl GooseResponse {
722    pub fn new(request: GooseRequestMetric, response: Result<Response, reqwest::Error>) -> Self {
723        GooseResponse { request, response }
724    }
725}
726
727/// Object created by [`log_debug()`](struct.GooseUser.html#method.log_debug) and written
728/// to log to assist in debugging.
729#[derive(Debug, Deserialize, Serialize)]
730pub struct GooseDebug {
731    /// String to identify the source of the log message.
732    pub tag: String,
733    /// Optional request made.
734    pub request: Option<GooseRequestMetric>,
735    /// Optional headers returned by server.
736    pub header: Option<String>,
737    /// Optional body text returned by server.
738    pub body: Option<String>,
739}
740impl GooseDebug {
741    fn new(
742        tag: &str,
743        request: Option<&GooseRequestMetric>,
744        header: Option<&header::HeaderMap>,
745        body: Option<&str>,
746    ) -> Self {
747        GooseDebug {
748            // Convert tag from &str to string.
749            tag: tag.to_string(),
750            // If request is defined, clone it.
751            request: request.cloned(),
752            // If header is defined, convert it to a string.
753            header: header.map(|h| format!("{:?}", h)),
754            // If header is defined, convert from &str to string.
755            body: body.map(|b| b.to_string()),
756        }
757    }
758}
759
760/// Used internally by Coordinated Omission Mitigation, tracks the cadence between when the same request
761/// is made as Goose loops through a Scenario.
762#[derive(Debug, Clone)]
763struct GooseRequestCadence {
764    /// The last time this GooseUser lopped through its Transactions.
765    last_time: std::time::Instant,
766    /// Total milliseconds of delays followed each Transaction. This has to be substracted out as it's
767    /// not impacted by the upstream server and it can change randomly affecting the cadence.
768    delays_since_last_time: u64,
769    /// How many times this GooseUser has looped through all of its Transactions.
770    counter: u64,
771    /// The minimum time taken to loop through all Transactions.
772    minimum_cadence: u64,
773    /// The maximum time taken to loop through all Transactions.
774    maximum_cadence: u64,
775    /// Average amount of time taken to loop through all Transactions.
776    average_cadence: u64,
777    /// Total amount of time spent processing Transactions.
778    total_elapsed: u64,
779    /// If non-zero, the length of the server slowdown detected by the Goose Coordinated
780    /// Omission Mitigation in milliseconds.
781    coordinated_omission_mitigation: u64,
782    /// The expected cadence to loop through all Transactions.
783    user_cadence: u64,
784    /// If -1 coordinated_omission_mitigation was never enabled. Otherwise is a counter of how
785    /// many times the mitigation triggered.
786    coordinated_omission_counter: isize,
787}
788impl GooseRequestCadence {
789    // Return a new, empty RequestCadence object.
790    fn new() -> GooseRequestCadence {
791        GooseRequestCadence {
792            last_time: std::time::Instant::now(),
793            delays_since_last_time: 0,
794            counter: 0,
795            minimum_cadence: 0,
796            maximum_cadence: 0,
797            average_cadence: 0,
798            total_elapsed: 0,
799            coordinated_omission_mitigation: 0,
800            user_cadence: 0,
801            coordinated_omission_counter: -1,
802        }
803    }
804}
805
806/// A marker trait representing user data of any type
807/// ([generic](https://doc.rust-lang.org/rust-by-example/generics.html)) that can
808/// be added to any [`GooseUser`](../goose/struct.GooseUser.html). The format of
809/// the data stored in `GooseUserData` must be defined in your load test, and by
810/// default supports any type that supports
811/// [`Send`](https://doc.rust-lang.org/std/marker/trait.Send.html) and
812/// [`Sync`](https://doc.rust-lang.org/std/marker/trait.Sync.html).
813///
814/// Stored in the [`GooseUser`] object in a private `session_data` field. Per-user
815/// session data is stored by invoking [`GooseUser::set_session_data`]. The session
816/// data can be accessed by invoking [`GooseUser::get_session_data`],
817/// [`GooseUser::get_session_data_mut`], [`GooseUser::get_session_data_unchecked`],
818/// or [`GooseUser::get_session_data_unchecked_mut`].
819///
820/// For an example, see
821/// [`examples/simple_with_session`](https://github.com/tag1consulting/goose/blob/main/examples/simple_with_session.rs).
822pub trait GooseUserData: Downcast + Send + Sync + 'static {}
823impl_downcast!(GooseUserData);
824impl<T: Send + Sync + 'static> GooseUserData for T {}
825
826trait CloneGooseUserData {
827    fn clone_goose_user_data(&self) -> Box<dyn GooseUserData>;
828}
829
830impl<T> CloneGooseUserData for T
831where
832    T: GooseUserData + Clone + 'static,
833{
834    fn clone_goose_user_data(&self) -> Box<dyn GooseUserData> {
835        Box::new(self.clone())
836    }
837}
838
839impl Clone for Box<dyn GooseUserData> {
840    fn clone(&self) -> Self {
841        self.clone_goose_user_data()
842    }
843}
844
845/// An individual user state, repeatedly running all [`Transaction`](./struct.Transaction.html)s
846/// in a specific [`Scenario`](./struct.Scenario.html).
847#[derive(Debug)]
848pub struct GooseUser {
849    /// The Instant when this `GooseUser` client started.
850    pub started: Instant,
851    /// How many iterations of the scenario this GooseUser has run.
852    pub(crate) iterations: usize,
853    /// An index into the internal [`GooseAttack`](../struct.GooseAttack.html)`.scenarios`
854    /// vector, indicating which [`Scenario`](./struct.Scenario.html) is running.
855    pub(crate) scenarios_index: usize,
856    /// The Scenario this GooseUser is running.
857    pub(crate) scenario_name: String,
858    /// An optional index into [`Scenario`]`.transaction`, indicating which transaction this is.
859    pub(crate) transaction_index: Option<String>,
860    /// Current transaction name, if set.
861    pub(crate) transaction_name: Option<String>,
862    /// Client used to make requests, managing sessions and cookies.
863    pub client: Client,
864    /// The base URL to prepend to all relative paths.
865    pub base_url: Url,
866    /// A local copy of the global [`GooseConfiguration`](../struct.GooseConfiguration.html).
867    pub config: GooseConfiguration,
868    /// Channel to logger.
869    pub logger: Option<flume::Sender<Option<GooseLog>>>,
870    /// Channel to throttle.
871    pub throttle: Option<flume::Sender<bool>>,
872    /// Normal transactions are optionally throttled,
873    /// [`test_start`](../struct.GooseAttack.html#method.test_start) and
874    /// [`test_stop`](../struct.GooseAttack.html#method.test_stop) transactions are not.
875    pub is_throttled: bool,
876    /// Channel for sending metrics to the parent for aggregation.
877    pub metrics_channel: Option<flume::Sender<GooseMetric>>,
878    /// Channel for notifying the parent when thread shuts down.
879    pub shutdown_channel: Option<flume::Sender<usize>>,
880    /// An index into the internal [`GooseAttack`](../struct.GooseAttack.html)`.weighted_users`
881    /// vector, indicating which weighted `GooseUser` is running.
882    pub weighted_users_index: usize,
883    /// Load test hash.
884    pub load_test_hash: u64,
885    /// Tracks the cadence that this user is looping through all Transactions, used by Coordinated
886    /// Omission Mitigation.
887    request_cadence: GooseRequestCadence,
888    /// Tracks how much time is spent sleeping during a loop through all transactions.
889    pub(crate) slept: u64,
890    /// Optional per-user session data of a generic type implementing the
891    /// [`GooseUserData`] trait.
892    session_data: Option<Box<dyn GooseUserData>>,
893}
894
895impl Clone for GooseUser {
896    fn clone(&self) -> Self {
897        Self {
898            started: self.started,
899            iterations: self.iterations,
900            scenarios_index: self.scenarios_index,
901            scenario_name: self.scenario_name.clone(),
902            transaction_index: self.transaction_index.clone(),
903            transaction_name: self.transaction_name.clone(),
904            client: self.client.clone(),
905            base_url: self.base_url.clone(),
906            config: self.config.clone(),
907            logger: self.logger.clone(),
908            throttle: self.throttle.clone(),
909            is_throttled: self.is_throttled,
910            metrics_channel: self.metrics_channel.clone(),
911            shutdown_channel: self.shutdown_channel.clone(),
912            weighted_users_index: self.weighted_users_index,
913            load_test_hash: self.load_test_hash,
914            request_cadence: self.request_cadence.clone(),
915            slept: self.slept,
916            session_data: if self.session_data.is_some() {
917                Option::from(self.session_data.clone_goose_user_data())
918            } else {
919                None
920            },
921        }
922    }
923}
924
925impl Debug for dyn GooseUserData {
926    fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
927        write!(fmt, "GooseUserData")
928    }
929}
930
931impl GooseUser {
932    /// Create a new user state.
933    pub fn new(
934        scenarios_index: usize,
935        scenario_name: String,
936        base_url: Url,
937        configuration: &GooseConfiguration,
938        load_test_hash: u64,
939        reqwest_client: Option<Client>,
940    ) -> Result<Self, GooseError> {
941        trace!("new GooseUser");
942
943        let client = match reqwest_client {
944            Some(c) => c,
945            None => create_reqwest_client(configuration)?,
946        };
947
948        Ok(GooseUser {
949            started: Instant::now(),
950            iterations: 0,
951            scenarios_index,
952            scenario_name,
953            transaction_index: None,
954            transaction_name: None,
955            client,
956            base_url,
957            config: configuration.clone(),
958            logger: None,
959            throttle: None,
960            is_throttled: true,
961            metrics_channel: None,
962            shutdown_channel: None,
963            // A value of max_value() indicates this user isn't fully initialized yet.
964            weighted_users_index: usize::MAX,
965            load_test_hash,
966            request_cadence: GooseRequestCadence::new(),
967            slept: 0,
968            session_data: None,
969        })
970    }
971
972    /// Create a new single-use user.
973    pub fn single(base_url: Url, configuration: &GooseConfiguration) -> Result<Self, GooseError> {
974        let mut single_user = GooseUser::new(0, "".to_string(), base_url, configuration, 0, None)?;
975        // Only one user, so index is 0.
976        single_user.weighted_users_index = 0;
977        // Do not throttle [`test_start`](../struct.GooseAttack.html#method.test_start) (setup) and
978        // [`test_stop`](../struct.GooseAttack.html#method.test_stop) (teardown) transactions.
979        single_user.is_throttled = false;
980
981        Ok(single_user)
982    }
983
984    /// Returns the number of iterations this GooseUser has run through it's
985    /// assigned [`Scenario`].
986    pub fn get_iterations(&self) -> usize {
987        self.iterations
988    }
989
990    /// Returns an optional reference to per-[`GooseUser`] session data.
991    ///
992    /// Leaves the session data in-place, returning an optional reference to the
993    /// original session data if existing and of the correct type. Returns [`None`]
994    /// if no session data has been set or the session data set is not of type `T`.
995    ///
996    /// # Example
997    /// ```rust
998    /// use goose::prelude::*;
999    ///
1000    /// struct Foo(String);
1001    ///
1002    /// let mut transaction = transaction!(get_session_data_function);
1003    ///
1004    /// /// A very simple transaction that makes a GET request.
1005    /// async fn get_session_data_function(user: &mut GooseUser) -> TransactionResult {
1006    ///     let foo = user.get_session_data::<Foo>().expect("Missing session data!");
1007    ///     println!("Session data: {}", foo.0);
1008    ///
1009    ///     Ok(())
1010    /// }
1011    /// ```
1012    pub fn get_session_data<T: GooseUserData>(&self) -> Option<&T> {
1013        match &self.session_data {
1014            Some(data) => data.downcast_ref::<T>(),
1015            None => None,
1016        }
1017    }
1018
1019    /// Returns a reference to per-[`GooseUser`] session data, without doing any
1020    /// validation that the session data exists and is of the correct type.
1021    ///
1022    /// Leaves the session data in-place, returning a reference to the original
1023    /// session data. Calling this method on a [`GooseUser`] object without
1024    /// session data or with a different type `T` will panic.
1025    ///
1026    /// For a safe alternative see [`GooseUser::get_session_data`].
1027    ///
1028    /// # Example
1029    /// ```rust
1030    /// use goose::prelude::*;
1031    ///
1032    /// struct Foo(String);
1033    ///
1034    /// let mut transaction = transaction!(get_session_data_unchecked_function);
1035    ///
1036    /// /// A very simple transaction that makes a GET request.
1037    /// async fn get_session_data_unchecked_function(user: &mut GooseUser) -> TransactionResult {
1038    ///     let foo = user.get_session_data_unchecked::<Foo>();
1039    ///     println!("Session data: {}", foo.0);
1040    ///
1041    ///     Ok(())
1042    /// }
1043    /// ```
1044    pub fn get_session_data_unchecked<T: GooseUserData>(&self) -> &T {
1045        let session_data = self.session_data.as_deref().expect("Missing session data!");
1046
1047        session_data
1048            .downcast_ref::<T>()
1049            .expect("Invalid session data!")
1050    }
1051
1052    /// Returns an optional mutable reference to per-[`GooseUser`] session data.
1053    ///
1054    /// Leaves the session data in-place, returning an optional mutable reference
1055    /// to the original session data if existing and of the correct type. Returns
1056    /// [`None`] if no session data has been set or the session data set is not of
1057    /// type `T`.
1058    ///
1059    /// # Example
1060    /// ```rust
1061    /// use goose::prelude::*;
1062    ///
1063    /// struct Foo(String);
1064    ///
1065    /// let mut transaction = transaction!(get_session_data_mut_function);
1066    ///
1067    /// /// A very simple transaction that makes a GET request.
1068    /// async fn get_session_data_mut_function(user: &mut GooseUser) -> TransactionResult {
1069    ///     let foo = user.get_session_data_mut::<Foo>().expect("Missing session data!");
1070    ///     foo.0 = "Bar".to_owned();
1071    ///     Ok(())
1072    /// }
1073    /// ```
1074    pub fn get_session_data_mut<T: GooseUserData>(&mut self) -> Option<&mut T> {
1075        match &mut self.session_data {
1076            Some(data) => data.downcast_mut::<T>(),
1077            None => None,
1078        }
1079    }
1080
1081    /// Returns a mutable reference to per-[`GooseUser`] session data, without
1082    /// doing any validation that the session data exists and is of the correct
1083    /// type.
1084    ///
1085    /// Leaves the session data in-place, returning a mutable reference to the
1086    /// original session data. Calling this method on a [`GooseUser`] object
1087    /// without session data or with a different type `T` will panic.
1088    ///
1089    /// For a safe alternative see [`GooseUser::get_session_data_mut`].
1090    ///
1091    /// # Example
1092    /// ```rust
1093    /// use goose::prelude::*;
1094    ///
1095    /// struct Foo(String);
1096    ///
1097    /// let mut transaction = transaction!(get_session_data_unchecked_mut_function);
1098    ///
1099    /// /// A very simple transaction that makes a GET request.
1100    /// async fn get_session_data_unchecked_mut_function(user: &mut GooseUser) -> TransactionResult {
1101    ///     let foo = user.get_session_data_unchecked_mut::<Foo>();
1102    ///     foo.0 = "Bar".to_owned();
1103    ///     Ok(())
1104    /// }
1105    /// ```
1106    pub fn get_session_data_unchecked_mut<T: GooseUserData>(&mut self) -> &mut T {
1107        let session_data = self
1108            .session_data
1109            .as_deref_mut()
1110            .expect("Missing session data!");
1111        session_data
1112            .downcast_mut::<T>()
1113            .expect("Invalid session data!")
1114    }
1115
1116    /// Sets session data for the current [`GooseUser`].
1117    ///
1118    /// If session data already exists for the current [`GooseUser`], it will be
1119    /// replaced. Session data must be of a type implementing the
1120    /// [`GooseUserData`] trait.
1121    ///
1122    /// # Example
1123    /// ```rust
1124    /// use goose::prelude::*;
1125    ///
1126    /// struct Foo(String);
1127    ///
1128    /// let mut transaction = transaction!(set_session_data_function);
1129    ///
1130    /// /// A very simple transaction that makes a GET request.
1131    /// async fn set_session_data_function(user: &mut GooseUser) -> TransactionResult {
1132    ///     user.set_session_data(Foo("Foo".to_string()));
1133    ///
1134    ///     Ok(())
1135    /// }
1136    /// ```
1137    pub fn set_session_data<T: GooseUserData>(&mut self, data: T) {
1138        self.session_data.replace(Box::new(data));
1139    }
1140
1141    /// A helper that prepends a `base_url` to all relative paths.
1142    ///
1143    /// A `base_url` is determined per user thread, using the following order
1144    /// of precedence:
1145    ///  1. `--host` (host specified on the command line when running load test)
1146    ///  2. [`Scenario`](./struct.Scenario.html)`.host` (default host defined for the
1147    ///     current scenario)
1148    ///  3. [`GooseDefault::Host`](../config/enum.GooseDefault.html#variant.Host) (default host
1149    ///     defined for the current load test)
1150    pub fn build_url(&self, path: &str) -> Result<String, Box<TransactionError>> {
1151        // If URL includes a host, simply use it.
1152        if let Ok(parsed_path) = Url::parse(path) {
1153            if let Some(_host) = parsed_path.host() {
1154                return Ok(path.to_string());
1155            }
1156        }
1157
1158        // Otherwise use the `base_url`.
1159        match self.base_url.join(path) {
1160            Ok(u) => Ok(u.to_string()),
1161            Err(e) => Err(Box::new(e.into())),
1162        }
1163    }
1164
1165    /// A helper to make a `GET` request of a path and collect relevant metrics.
1166    /// Automatically prepends the correct host.
1167    ///
1168    /// Calls to `get()` return a [`GooseResponse`](./struct.GooseResponse.html) object which
1169    /// contains a copy of the request you made ([`GooseRequestMetric`](./struct.GooseRequestMetric.html)),
1170    /// and the response ([`reqwest::Response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)).
1171    ///
1172    /// If you need to set headers, change timeouts, or otherwise make use of the
1173    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
1174    /// object, refer to [`GooseUser::get_request_builder`].
1175    ///
1176    /// # Example
1177    /// GET a URL.
1178    /// ```rust
1179    /// use goose::prelude::*;
1180    ///
1181    /// let mut transaction = transaction!(get_function);
1182    ///
1183    /// /// A very simple transaction that makes a GET request.
1184    /// async fn get_function(user: &mut GooseUser) -> TransactionResult {
1185    ///     let _goose = user.get("path/to/foo/").await?;
1186    ///
1187    ///     Ok(())
1188    /// }
1189    /// ```
1190    pub async fn get(&mut self, path: &str) -> Result<GooseResponse, Box<TransactionError>> {
1191        // GET path.
1192        let goose_request = GooseRequest::builder()
1193            .method(GooseMethod::Get)
1194            .path(path)
1195            .build();
1196
1197        // Make the request and return the GooseResponse.
1198        self.request(goose_request).await
1199    }
1200
1201    /// A helper to make a named `GET` request of a path and collect relevant metrics.
1202    /// Automatically prepends the correct host.
1203    ///
1204    /// Calls to `get_named()` return a [`GooseResponse`](./struct.GooseResponse.html) object which
1205    /// contains a copy of the request you made ([`GooseRequestMetric`](./struct.GooseRequestMetric.html)),
1206    /// and the response ([`reqwest::Response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)).
1207    ///
1208    /// If you need to set headers, change timeouts, or otherwise make use of the
1209    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
1210    /// object, refer to [`GooseUser::get_request_builder`].
1211    ///
1212    /// # Example
1213    /// GET a URL and name the request in collected metrics.
1214    /// ```rust
1215    /// use goose::prelude::*;
1216    ///
1217    /// let mut transaction = transaction!(get_function);
1218    ///
1219    /// /// A very simple transaction that makes a GET request, naming it for metrics.
1220    /// async fn get_function(user: &mut GooseUser) -> TransactionResult {
1221    ///     let _goose = user.get_named("path/to/foo/", "foo").await?;
1222    ///
1223    ///     Ok(())
1224    /// }
1225    /// ```
1226    pub async fn get_named(
1227        &mut self,
1228        path: &str,
1229        name: &str,
1230    ) -> Result<GooseResponse, Box<TransactionError>> {
1231        // GET path named.
1232        let goose_request = GooseRequest::builder()
1233            .method(GooseMethod::Get)
1234            .path(path)
1235            .name(name)
1236            .build();
1237
1238        // Make the request and return the GooseResponse.
1239        self.request(goose_request).await
1240    }
1241
1242    /// A helper to make a `POST` request of a path and collect relevant metrics.
1243    /// Automatically prepends the correct host.
1244    ///
1245    /// Calls to `post()` return a [`GooseResponse`](./struct.GooseResponse.html) object which
1246    /// contains a copy of the request you made ([`GooseRequestMetric`](./struct.GooseRequestMetric.html)),
1247    /// and the response ([`reqwest::Response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)).
1248    ///
1249    /// If you need to set headers, change timeouts, or otherwise make use of the
1250    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
1251    /// object, refer to [`GooseUser::get_request_builder`].
1252    ///
1253    /// # Example
1254    /// POST an arbitrary body.
1255    /// ```rust
1256    /// use goose::prelude::*;
1257    ///
1258    /// let mut transaction = transaction!(post_function);
1259    ///
1260    /// /// A very simple transaction that makes a POST request.
1261    /// async fn post_function(user: &mut GooseUser) -> TransactionResult {
1262    ///     let _goose = user.post("path/to/foo/", "BODY BEING POSTED").await?;
1263    ///
1264    ///     Ok(())
1265    /// }
1266    /// ```
1267    pub async fn post<T: Into<reqwest::Body>>(
1268        &mut self,
1269        path: &str,
1270        body: T,
1271    ) -> Result<GooseResponse, Box<TransactionError>> {
1272        // Build a Reqwest RequestBuilder object.
1273        let url = self.build_url(path)?;
1274        let reqwest_request_builder = self.client.post(url);
1275
1276        // POST request.
1277        let goose_request = GooseRequest::builder()
1278            .method(GooseMethod::Post)
1279            .path(path)
1280            .set_request_builder(reqwest_request_builder.body(body))
1281            .build();
1282
1283        // Make the request and return the GooseResponse.
1284        self.request(goose_request).await
1285    }
1286
1287    /// A helper to make a `POST` request of a form on a path and collect relevant metrics.
1288    /// Automatically prepends the correct host.
1289    ///
1290    /// Calls to `post_form()` return a [`GooseResponse`](./struct.GooseResponse.html) object which
1291    /// contains a copy of the request you made ([`GooseRequestMetric`](./struct.GooseRequestMetric.html)),
1292    /// and the response ([`reqwest::Response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)).
1293    ///
1294    /// If you need to set headers, change timeouts, or otherwise make use of the
1295    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
1296    /// object, refer to [`GooseUser::get_request_builder`].
1297    ///
1298    /// # Example
1299    /// POST a form.
1300    /// ```rust
1301    /// use goose::prelude::*;
1302    ///
1303    /// let mut transaction = transaction!(post_function);
1304    ///
1305    /// /// A very simple transaction that POSTs form parameters.
1306    /// async fn post_function(user: &mut GooseUser) -> TransactionResult {
1307    ///     let params = [("foo", "bar"), ("foo2", "bar2")];
1308    ///     let _goose = user.post_form("path/to/foo/", &params).await?;
1309    ///
1310    ///     Ok(())
1311    /// }
1312    /// ```
1313    pub async fn post_form<T: Serialize + ?Sized>(
1314        &mut self,
1315        path: &str,
1316        form: &T,
1317    ) -> Result<GooseResponse, Box<TransactionError>> {
1318        // Build a Reqwest RequestBuilder object.
1319        let url = self.build_url(path)?;
1320        let reqwest_request_builder = self.client.post(url);
1321
1322        // POST form request.
1323        let goose_request = GooseRequest::builder()
1324            .method(GooseMethod::Post)
1325            .path(path)
1326            .set_request_builder(reqwest_request_builder.form(&form))
1327            .build();
1328
1329        // Make the request and return the GooseResponse.
1330        self.request(goose_request).await
1331    }
1332
1333    /// A helper to make a `POST` request of json on a path and collect relevant metrics.
1334    /// Automatically prepends the correct host.
1335    ///
1336    /// Calls to `post_json()` return a [`GooseResponse`](./struct.GooseResponse.html) object which
1337    /// contains a copy of the request you made ([`GooseRequestMetric`](./struct.GooseRequestMetric.html)),
1338    /// and the response ([`reqwest::Response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)).
1339    ///
1340    /// If you need to set headers, change timeouts, or otherwise make use of the
1341    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
1342    /// object, refer to [`GooseUser::get_request_builder`].
1343    ///
1344    /// # Example
1345    /// POST an arbitrary JSON object.
1346    /// ```rust
1347    /// use goose::prelude::*;
1348    ///
1349    /// let mut transaction = transaction!(post_function);
1350    ///
1351    /// /// A very simple transaction that POSTs an arbitrary json object.
1352    /// async fn post_function(user: &mut GooseUser) -> TransactionResult {
1353    ///     let json = &serde_json::json!({
1354    ///         "foo": "bar",
1355    ///         "foo2": "bar2"
1356    ///     });
1357    ///     let _goose = user.post_json("path/to/foo/", &json).await?;
1358    ///
1359    ///     Ok(())
1360    /// }
1361    /// ```
1362    pub async fn post_json<T: Serialize + ?Sized>(
1363        &mut self,
1364        path: &str,
1365        json: &T,
1366    ) -> Result<GooseResponse, Box<TransactionError>> {
1367        // Build a Reqwest RequestBuilder object.
1368        let url = self.build_url(path)?;
1369        let reqwest_request_builder = self.client.post(url);
1370
1371        // POST json request.
1372        let goose_request = GooseRequest::builder()
1373            .method(GooseMethod::Post)
1374            .path(path)
1375            .set_request_builder(reqwest_request_builder.json(&json))
1376            .build();
1377
1378        // Make the request and return the GooseResponse.
1379        self.request(goose_request).await
1380    }
1381
1382    /// A helper to make a `HEAD` request of a path and collect relevant metrics.
1383    /// Automatically prepends the correct host.
1384    ///
1385    /// Calls to `head()` return a [`GooseResponse`](./struct.GooseResponse.html) object which
1386    /// contains a copy of the request you made ([`GooseRequestMetric`](./struct.GooseRequestMetric.html)),
1387    /// and the response ([`reqwest::Response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)).
1388    ///
1389    /// If you need to set headers, change timeouts, or otherwise make use of the
1390    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
1391    /// object, refer to [`GooseUser::get_request_builder`].
1392    ///
1393    /// # Example
1394    /// Make a HEAD request.
1395    /// ```rust
1396    /// use goose::prelude::*;
1397    ///
1398    /// let mut transaction = transaction!(head_function);
1399    ///
1400    /// /// A very simple transaction that makes a HEAD request.
1401    /// async fn head_function(user: &mut GooseUser) -> TransactionResult {
1402    ///     let _goose = user.head("path/to/foo/").await?;
1403    ///
1404    ///     Ok(())
1405    /// }
1406    /// ```
1407    pub async fn head(&mut self, path: &str) -> Result<GooseResponse, Box<TransactionError>> {
1408        // HEAD request.
1409        let goose_request = GooseRequest::builder()
1410            .method(GooseMethod::Head)
1411            .path(path)
1412            .build();
1413
1414        // Make the request and return the GooseResponse.
1415        self.request(goose_request).await
1416    }
1417
1418    /// A helper to make a `DELETE` request of a path and collect relevant metrics.
1419    /// Automatically prepends the correct host.
1420    ///
1421    /// Calls to `delete()` return a [`GooseResponse`](./struct.GooseResponse.html) object which
1422    /// contains a copy of the request you made ([`GooseRequestMetric`](./struct.GooseRequestMetric.html)),
1423    /// and the response ([`reqwest::Response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)).
1424    ///
1425    /// If you need to set headers, change timeouts, or otherwise make use of the
1426    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
1427    /// object, refer to [`GooseUser::get_request_builder`].
1428    ///
1429    /// # Example
1430    /// Make a DELETE request.
1431    /// ```rust
1432    /// use goose::prelude::*;
1433    ///
1434    /// let mut transaction = transaction!(delete_function);
1435    ///
1436    /// /// A very simple transaction that makes a DELETE request.
1437    /// async fn delete_function(user: &mut GooseUser) -> TransactionResult {
1438    ///     let _goose = user.delete("path/to/foo/").await?;
1439    ///
1440    ///     Ok(())
1441    /// }
1442    /// ```
1443    pub async fn delete(&mut self, path: &str) -> Result<GooseResponse, Box<TransactionError>> {
1444        // DELETE request.
1445        let goose_request = GooseRequest::builder()
1446            .method(GooseMethod::Delete)
1447            .path(path)
1448            .build();
1449
1450        // Make the request and return the GooseResponse.
1451        self.request(goose_request).await
1452    }
1453
1454    /// Used to get a [`reqwest::RequestBuilder`] object. If no [`reqwest::RequestBuilder`] is
1455    /// already defined in the [`GooseRequest`] passed to [`GooseUser::request`] it will automatically
1456    /// invoke this function.
1457    ///
1458    /// The HTTP request method must be defined as a [`GooseMethod`], and the path that will be requested
1459    /// must be defined as a [`&str`].
1460    ///
1461    /// It is possible to use this function to directly interact with the [`reqwest::RequestBuilder`]
1462    /// object and the [`GooseRequest`] object during load tests. In the following example, we set a
1463    /// timeout on the Request, and tell Goose to expect a 404 HTTP response status code.
1464    ///
1465    /// # Example
1466    /// Request a non-existent page, timing out after 500 milliseconds.
1467    /// ```rust
1468    /// use goose::prelude::*;
1469    ///
1470    /// let mut transaction = transaction!(test_404);
1471    ///
1472    /// async fn test_404(user: &mut GooseUser) -> TransactionResult {
1473    ///     use std::time::Duration;
1474    ///
1475    ///     // Manually interact with the Reqwest RequestBuilder object.
1476    ///     let request_builder = user.get_request_builder(&GooseMethod::Get, "no/such/path")?
1477    ///         // Configure the request to timeout if it takes longer than 500 milliseconds.
1478    ///         .timeout(Duration::from_millis(500));
1479    ///
1480    ///     // Manually build a GooseRequest.
1481    ///     let goose_request = GooseRequest::builder()
1482    ///         // Manually add our custom RequestBuilder object.
1483    ///         .set_request_builder(request_builder)
1484    ///         // Tell Goose to expect a 404 status code.
1485    ///         .expect_status_code(404)
1486    ///         // Turn the GooseRequestBuilder object into a GooseRequest.
1487    ///         .build();
1488    ///
1489    ///     // Finally make the actual request with our custom GooseRequest object.
1490    ///     let _goose = user.request(goose_request).await?;
1491    ///
1492    ///     Ok(())
1493    /// }
1494    /// ```
1495    pub fn get_request_builder(
1496        &self,
1497        method: &GooseMethod,
1498        path: &str,
1499    ) -> Result<RequestBuilder, Box<TransactionError>> {
1500        // Prepend the `base_url` to all relative paths.
1501        let url = self.build_url(path)?;
1502
1503        // Invoke appropriate Reqwest convenience function to generate an
1504        // appropriate RequestBuilder.
1505        Ok(match method {
1506            GooseMethod::Delete => self.client.delete(&url),
1507            GooseMethod::Get => self.client.get(&url),
1508            GooseMethod::Head => self.client.head(&url),
1509            GooseMethod::Patch => self.client.patch(&url),
1510            GooseMethod::Post => self.client.post(&url),
1511            GooseMethod::Put => self.client.put(&url),
1512        })
1513    }
1514
1515    /// Makes a request for the provided [`GooseRequest`] object, and if metrics are enabled
1516    /// captures relevant metrics.
1517    ///
1518    /// Calls to `request()` return a [`Result`] containing a [`GooseResponse`] on success, and a
1519    /// [`flume::SendError`](https://docs.rs/flume/*/flume/struct.SendError.html)`<bool>`,
1520    /// on failure. Failure only happens when `--throttle-requests` is enabled and the load test
1521    /// completes. The [`GooseResponse`](./struct.GooseResponse.html) object contains a copy of
1522    /// the request you made ([`GooseRequestMetric`](./struct.GooseRequestMetric.html)), and the
1523    /// response ([`reqwest::Response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)).
1524    ///
1525    /// # Example
1526    /// Make a GET request.
1527    /// ```rust
1528    /// use goose::prelude::*;
1529    ///
1530    /// let mut transaction = transaction!(get_function);
1531    ///
1532    /// /// A simple transaction that makes a GET request.
1533    /// async fn get_function(user: &mut GooseUser) -> TransactionResult {
1534    ///     let goose_request = GooseRequest::builder()
1535    ///       // Goose will prepend a host name to this path.
1536    ///       .path("path/to/loadtest")
1537    ///       // GET is the default method, this is not necessary.
1538    ///       .method(GooseMethod::Get)
1539    ///       // Assemble the `GooseRequestBuilder` into a `GooseRequest.
1540    ///       .build();
1541    ///     let goose = user.request(goose_request).await?;
1542    ///
1543    ///     // Do stuff with goose.request and/or goose.response here.
1544    ///
1545    ///     Ok(())
1546    /// }
1547    /// ```
1548    pub async fn request(
1549        &mut self,
1550        mut request: GooseRequest<'_>,
1551    ) -> Result<GooseResponse, Box<TransactionError>> {
1552        // If the RequestBuilder is already defined in the GooseRequest use it.
1553        let request_builder = if request.request_builder.is_some() {
1554            request.request_builder.take().unwrap()
1555        // Otherwise get a new RequestBuilder.
1556        } else {
1557            self.get_request_builder(&request.method, request.path)?
1558        };
1559
1560        // Determine the name for this request.
1561        let request_name = self.get_request_name(&request);
1562
1563        // If throttle-requests is enabled...
1564        if self.is_throttled && self.throttle.is_some() {
1565            // ...wait until there's room to add a token to the throttle channel before proceeding.
1566            debug!("GooseUser: waiting on throttle");
1567            // Will result in TransactionError::RequestCanceled if this fails.
1568            if let Err(e) = self.throttle.clone().unwrap().send_async(true).await {
1569                return Err(Box::new(e.into()));
1570            }
1571        };
1572
1573        // Once past the throttle, the request is officially started.
1574        let started = Instant::now();
1575
1576        // Create a Reqwest Request object from the RequestBuilder.
1577        let built_request = match request_builder.build() {
1578            Ok(r) => r,
1579            Err(e) => return Err(Box::new(e.into())),
1580        };
1581
1582        // Get a string version of request path for logging.
1583        let path = match Url::parse(built_request.url().as_ref()) {
1584            Ok(u) => u.path().to_string(),
1585            Err(e) => {
1586                error!("failed to parse url: {}", e);
1587                "".to_string()
1588            }
1589        };
1590
1591        // Grab a copy of any headers set by this request, included in the request log
1592        // and the debug log.
1593        let mut headers: Vec<String> = Vec::new();
1594        for header in built_request.headers() {
1595            headers.push(format!("{:?}", header));
1596        }
1597
1598        // If enabled, grab a copy of the request body, included in the request log and
1599        // the debug log.
1600        let body = if self.config.request_body {
1601            // Get a bytes representation of the body, if any.
1602            let body_bytes = match built_request.body() {
1603                Some(b) => b.as_bytes().unwrap_or(b""),
1604                None => b"",
1605            };
1606            // Convert the bytes into a &str if valid utf8.
1607            str::from_utf8(body_bytes).unwrap_or("")
1608        } else {
1609            ""
1610        };
1611
1612        // Record the complete client request, included in the request log and the debug log.
1613        let raw_request = GooseRawRequest::new(
1614            goose_method_from_method(built_request.method().clone())?,
1615            built_request.url().as_str(),
1616            headers,
1617            body,
1618        );
1619
1620        // Provide details about the current transaction for logging.
1621        let transaction_detail = TransactionDetail {
1622            scenario_index: self.scenarios_index,
1623            scenario_name: self.scenario_name.as_str(),
1624            transaction_index: self
1625                .transaction_index
1626                .as_ref()
1627                .map_or_else(|| "", |v| v.as_ref()),
1628            transaction_name: self
1629                .transaction_name
1630                .as_ref()
1631                .map_or_else(|| "", |v| v.as_ref()),
1632        };
1633
1634        // Record information about the request.
1635        let mut request_metric = GooseRequestMetric::new(
1636            raw_request,
1637            transaction_detail,
1638            request_name,
1639            self.started.elapsed().as_millis(),
1640            self.weighted_users_index,
1641        );
1642
1643        // Make the actual request.
1644        let response = self.client.execute(built_request).await;
1645        request_metric.set_response_time(started.elapsed().as_millis());
1646
1647        // Determine if the request suceeded or failed.
1648        match &response {
1649            Ok(r) => {
1650                let status_code = r.status();
1651                debug!("{:?}: status_code {}", &path, status_code);
1652
1653                // Update the request_metric object.
1654                request_metric.set_status_code(Some(status_code));
1655                request_metric.set_final_url(r.url().as_str());
1656
1657                // Check if we were expecting a specific status code.
1658                if let Some(expect_status_code) = request.expect_status_code {
1659                    // Record a failure if the expected status code was not returned.
1660                    if status_code != expect_status_code {
1661                        request_metric.success = false;
1662                        request_metric.error = format!("{}: {}", status_code, request_name);
1663                    }
1664                // Otherwise record a failure if the returned status code was not a success.
1665                } else if !status_code.is_success() {
1666                    request_metric.success = false;
1667                    request_metric.error = format!("{}: {}", status_code, request_name);
1668                }
1669
1670                // Load test user was redirected.
1671                if self.config.sticky_follow && request_metric.raw.url != request_metric.final_url {
1672                    let base_url = self.base_url.to_string();
1673                    // Check if the URL redirected started with the load test base_url.
1674                    if !request_metric.final_url.starts_with(&base_url) {
1675                        let redirected_url = match Url::parse(&request_metric.final_url) {
1676                            Ok(u) => u,
1677                            Err(e) => return Err(Box::new(e.into())),
1678                        };
1679                        let redirected_base_url =
1680                            redirected_url[..url::Position::BeforePath].to_string();
1681                        info!(
1682                            "base_url for user {} redirected from {} to {}",
1683                            self.weighted_users_index + 1,
1684                            &base_url,
1685                            &redirected_base_url
1686                        );
1687                        let _ = self.set_base_url(&redirected_base_url);
1688                    }
1689                }
1690            }
1691            Err(e) => {
1692                // @TODO: what can we learn from a reqwest error?
1693                warn!("{:?}: {}", &path, e);
1694                request_metric.success = false;
1695                request_metric.set_status_code(None);
1696                request_metric.error = clean_reqwest_error(e, request_name);
1697            }
1698        };
1699
1700        // If enabled, track the cadence between each time the same request is made while
1701        // this GooseUser is running. If requests are blocked by the upstream server, this
1702        // allows Goose to backfill the requests that should have been made based on
1703        // cadence statistics.
1704        request_metric.user_cadence = self
1705            .coordinated_omission_mitigation(&request_metric)
1706            .await?;
1707
1708        // Send a copy of the raw request object to the parent process if
1709        // we're tracking metrics.
1710        if !self.config.no_metrics {
1711            self.send_request_metric_to_parent(request_metric.clone())?;
1712        }
1713
1714        if request.error_on_fail && !request_metric.success {
1715            error!("{:?} {}", &path, &request_metric.error);
1716            return Err(Box::new(TransactionError::RequestFailed {
1717                raw_request: request_metric,
1718            }));
1719        }
1720
1721        Ok(GooseResponse::new(request_metric, response))
1722    }
1723
1724    /// Tracks the time it takes for the current GooseUser to loop through all Transactions
1725    /// if Coordinated Omission Mitigation is enabled.
1726    pub(crate) async fn update_request_cadence(&mut self, thread_number: usize) {
1727        if let Some(co_mitigation) = self.config.co_mitigation.as_ref() {
1728            // Return immediately if coordinated omission mitigation is disabled.
1729            if co_mitigation == &GooseCoordinatedOmissionMitigation::Disabled {
1730                return;
1731            }
1732
1733            // Grab the current timestamp to calculate the difference since the last
1734            // time through the loop.
1735            let now = std::time::Instant::now();
1736
1737            // Swap out the `slept` counter, which is the total time the GooseUser slept
1738            // between transactions, a potentially randomly changing value. Reset to 0 for the
1739            // next loop through all Transactions.
1740            self.request_cadence.delays_since_last_time = self.slept;
1741            self.slept = 0;
1742
1743            // How much time passed since the last time this GooseUser looped through all
1744            // transactions, accounting for time waiting between Transactions due to `set_wait_time`.
1745            let elapsed = (now - self.request_cadence.last_time).as_millis() as u64
1746                - self.request_cadence.delays_since_last_time;
1747
1748            // Update `minimum_cadence` if this was the fastest seen.
1749            if elapsed < self.request_cadence.minimum_cadence
1750                || self.request_cadence.minimum_cadence == 0
1751            {
1752                self.request_cadence.minimum_cadence = elapsed;
1753            // Update `maximum_cadence` if this was the slowest seen.
1754            } else if elapsed > self.request_cadence.maximum_cadence {
1755                self.request_cadence.maximum_cadence = elapsed;
1756            }
1757
1758            // Update request_cadence metrics based on the timing of the current request.
1759            self.request_cadence.counter += 1;
1760            self.request_cadence.total_elapsed += elapsed;
1761            self.request_cadence.last_time = now;
1762            self.request_cadence.average_cadence =
1763                self.request_cadence.total_elapsed / self.request_cadence.counter;
1764
1765            if self.request_cadence.counter > 3 {
1766                if self.request_cadence.coordinated_omission_counter < 0 {
1767                    debug!(
1768                        "user {} enabled coordinated omission mitigation",
1769                        thread_number
1770                    );
1771                    self.request_cadence.coordinated_omission_counter += 1;
1772                }
1773                // Calculate the expected cadence for this Transaction request.
1774                let cadence = match co_mitigation {
1775                    // Expected cadence is the average time between requests.
1776                    GooseCoordinatedOmissionMitigation::Average => {
1777                        self.request_cadence.average_cadence
1778                    }
1779                    // Expected cadence is the maximum time between requests.
1780                    GooseCoordinatedOmissionMitigation::Maximum => {
1781                        self.request_cadence.maximum_cadence
1782                    }
1783                    // Expected cadence is the minimum time between requests.
1784                    GooseCoordinatedOmissionMitigation::Minimum => {
1785                        self.request_cadence.minimum_cadence
1786                    }
1787                    // This is not possible as we would have exited already if coordinated
1788                    // omission mitigation was disabled.
1789                    GooseCoordinatedOmissionMitigation::Disabled => unreachable!(),
1790                };
1791                if elapsed > (cadence * 2) {
1792                    debug!(
1793                        "user {}: coordinated_omission_mitigation: elapsed({}) > cadence({})",
1794                        thread_number, elapsed, cadence
1795                    );
1796                    self.request_cadence.coordinated_omission_counter += 1;
1797                    self.request_cadence.coordinated_omission_mitigation = elapsed;
1798                } else {
1799                    self.request_cadence.coordinated_omission_mitigation = 0;
1800                }
1801                // Always track the expected cadence.
1802                self.request_cadence.user_cadence = cadence;
1803            }
1804        } else {
1805            // Coordinated Omission Mitigation defaults to average.
1806            unreachable!();
1807        }
1808    }
1809
1810    /// If Coordinated Omission Mitigation is enabled, compares how long has passed since the last
1811    /// loop through all Transactions by the current GooseUser. Through this mechanism, Goose is
1812    /// able to detect stalls on the upstream server being load tested, backfilling requests based
1813    /// on what statistically should have happened. Can be disabled with `--co-mitigation disabled`.
1814    async fn coordinated_omission_mitigation(
1815        &self,
1816        request_metric: &GooseRequestMetric,
1817    ) -> Result<u64, Box<TransactionError>> {
1818        if let Some(co_mitigation) = self.config.co_mitigation.as_ref() {
1819            // Return immediately if coordinated omission mitigation is disabled.
1820            if co_mitigation == &GooseCoordinatedOmissionMitigation::Disabled {
1821                return Ok(0);
1822            }
1823
1824            // Generate an info level alert if this specific request took longer than the normal
1825            // cadence, as that means this specific request will likely trigger Coordinated
1826            // Omission Mitigation.
1827            if self.request_cadence.counter > 3
1828                && request_metric.response_time > self.request_cadence.user_cadence
1829            {
1830                let transaction_name = if let Some(transaction_name) = &self.transaction_name {
1831                    format!(", transaction name: \"{}\"", transaction_name)
1832                } else {
1833                    "".to_string()
1834                };
1835                info!(
1836                    "{:.3}s into goose attack: \"{} {}\" [{}] took abnormally long ({} ms){}",
1837                    request_metric.elapsed as f64 / 1_000.0,
1838                    request_metric.raw.method,
1839                    request_metric.raw.url,
1840                    request_metric.status_code,
1841                    request_metric.response_time,
1842                    transaction_name,
1843                );
1844            }
1845
1846            // Check if Coordinated Omission Mitigation has been triggered.
1847            if self.request_cadence.coordinated_omission_mitigation > 0 {
1848                // Base our coordinated omission generated request metric on the actual
1849                // metric that triggered this logic.
1850                let mut coordinated_omission_request_metric = request_metric.clone();
1851                // Record data points specific to coordinated_omission.
1852                coordinated_omission_request_metric.coordinated_omission_elapsed =
1853                    self.request_cadence.coordinated_omission_mitigation;
1854                // Record data points specific to coordinated_omission.
1855                coordinated_omission_request_metric.user_cadence =
1856                    self.request_cadence.user_cadence;
1857                // Send the coordinated omission mitigation generated metrics to the parent.
1858                self.send_request_metric_to_parent(coordinated_omission_request_metric)?;
1859            }
1860            Ok(self.request_cadence.user_cadence)
1861        } else {
1862            // A setting for coordinated omission mitigation is required, defaults to Average.
1863            unreachable!();
1864        }
1865    }
1866
1867    fn send_request_metric_to_parent(
1868        &self,
1869        request_metric: GooseRequestMetric,
1870    ) -> TransactionResult {
1871        // If requests-file is enabled, send a copy of the raw request to the logger thread.
1872        if !self.config.request_log.is_empty() {
1873            if let Some(logger) = self.logger.as_ref() {
1874                if let Err(e) = logger.send(Some(GooseLog::Request(request_metric.clone()))) {
1875                    return Err(Box::new(e.into()));
1876                }
1877            }
1878        }
1879
1880        // Parent is not defined when running
1881        // [`test_start`](../struct.GooseAttack.html#method.test_start),
1882        // [`test_stop`](../struct.GooseAttack.html#method.test_stop), and during testing.
1883        if let Some(metrics_channel) = self.metrics_channel.clone() {
1884            if let Err(e) = metrics_channel.send(GooseMetric::Request(Box::new(request_metric))) {
1885                return Err(Box::new(e.into()));
1886            }
1887        }
1888
1889        Ok(())
1890    }
1891
1892    /// If `request_name` is set, unwrap and use this. Otherwise, if the Transaction has a name
1893    /// set use it. Otherwise use the path.
1894    fn get_request_name<'a>(&'a self, request: &'a GooseRequest) -> &'a str {
1895        match request.name {
1896            // If a request.name is set, unwrap and return it.
1897            Some(rn) => rn,
1898            None => {
1899                // Otherwise determine if the current Transaction is named, and if so return it.
1900                if let Some(transaction_name) = &self.transaction_name {
1901                    transaction_name
1902                } else {
1903                    // Otherwise return a copy of the the path.
1904                    request.path
1905                }
1906            }
1907        }
1908    }
1909
1910    /// Manually mark a request as a success.
1911    ///
1912    /// Goose determines if a request was successful based on the the HTTP response status
1913    /// code. By default, it uses [`reqwest::StatusCode::is_success`]. If an alternative
1914    /// HTTP response code is expected, use [`GooseRequestBuilder::expect_status_code`]. If
1915    /// validation requires additional logic, you can use set_success().
1916    ///
1917    /// A copy of your original request is returned with the response, and a mutable copy
1918    /// must be included when setting a request as a success.
1919    ///
1920    /// # Example
1921    /// ```rust
1922    /// use goose::prelude::*;
1923    ///
1924    /// let mut transaction = transaction!(get_function);
1925    ///
1926    /// /// A simple transaction that makes a GET request.
1927    /// async fn get_function(user: &mut GooseUser) -> TransactionResult {
1928    ///     let mut goose = user.get("404").await?;
1929    ///
1930    ///     if let Ok(response) = &goose.response {
1931    ///         // We expect a 404 here.
1932    ///         if response.status() == 404 {
1933    ///             return user.set_success(&mut goose.request);
1934    ///         }
1935    ///     }
1936    ///
1937    ///     Err(Box::new(TransactionError::RequestFailed {
1938    ///         raw_request: goose.request.clone(),
1939    ///     }))
1940    /// }
1941    /// ````
1942    pub fn set_success(&self, request: &mut GooseRequestMetric) -> TransactionResult {
1943        // Only send update if this was previously not a success.
1944        if !request.success {
1945            request.success = true;
1946            request.update = true;
1947            self.send_request_metric_to_parent(request.clone())?;
1948        }
1949
1950        Ok(())
1951    }
1952
1953    /// Manually mark a request as a failure.
1954    ///
1955    /// By default, Goose will consider any response with a 2xx status code as a success.
1956    /// You may require more advanced logic, in which a 2xx status code is actually a
1957    /// failure. A copy of your original request is returned with the response, and a
1958    /// mutable copy must be included when setting a request as a failure.
1959    ///
1960    /// Calls to `set_failure` must include four parameters. The first, `tag`, is an
1961    /// arbitrary string identifying the reason for the failure, used when logging. The
1962    /// second, `request`, is a mutable reference to the
1963    /// ([`GooseRequestMetric`](./struct.GooseRequestMetric.html)) object of the request being
1964    /// identified as a failure (the contained `success` field will be set to `false`,
1965    /// and the `update` field will be set to `true`). The last two parameters, `header`
1966    /// and `body`, are optional and used to provide more detail in logs.
1967    ///
1968    /// The value of `tag` will normally be collected into the errors summary table if
1969    /// metrics are being displayed. However, if `set_failure` is called multiple times,
1970    /// or is called on a request that was already an error, only the first error will
1971    /// be collected.
1972    ///
1973    /// This also calls [`GooseUser::log_debug`].
1974    ///
1975    /// # Example
1976    /// ```rust
1977    /// use goose::prelude::*;
1978    ///
1979    /// let mut transaction = transaction!(loadtest_index_page);
1980    ///
1981    /// async fn loadtest_index_page(user: &mut GooseUser) -> TransactionResult {
1982    ///     let mut goose = user.get("").await?;
1983    ///
1984    ///     if let Ok(response) = goose.response {
1985    ///         // We only need to check pages that returned a success status code.
1986    ///         if response.status().is_success() {
1987    ///             match response.text().await {
1988    ///                 Ok(text) => {
1989    ///                     // If the expected string doesn't exist, this page load
1990    ///                     // was a failure.
1991    ///                     if !text.contains("this string must exist") {
1992    ///                         // As this is a named request, pass in the name not the URL
1993    ///                         return user.set_failure("string missing", &mut goose.request, None, None);
1994    ///                     }
1995    ///                 }
1996    ///                 // Empty page, this is a failure.
1997    ///                 Err(_) => {
1998    ///                     return user.set_failure("empty page", &mut goose.request, None, None);
1999    ///                 }
2000    ///             }
2001    ///         }
2002    ///     };
2003    ///
2004    ///     Ok(())
2005    /// }
2006    /// ````
2007    pub fn set_failure(
2008        &self,
2009        tag: &str,
2010        request: &mut GooseRequestMetric,
2011        headers: Option<&header::HeaderMap>,
2012        body: Option<&str>,
2013    ) -> TransactionResult {
2014        // Only send update if this was previously a success.
2015        if request.success {
2016            request.success = false;
2017            request.update = true;
2018            request.error = tag.to_string();
2019            self.send_request_metric_to_parent(request.clone())?;
2020        }
2021        // Write failure to log, converting `&mut request` to `&request` as needed by `log_debug()`.
2022        self.log_debug(tag, Some(&*request), headers, body)?;
2023
2024        // Print log to stdout.
2025        info!("set_failure: {}", tag);
2026
2027        Err(Box::new(TransactionError::RequestFailed {
2028            raw_request: request.clone(),
2029        }))
2030    }
2031
2032    /// Write to [`debug_file`](../struct.GooseConfiguration.html#structfield.debug_file)
2033    /// if enabled.
2034    ///
2035    /// This function provides a mechanism for optional debug logging when a load test
2036    /// is running. This can be especially helpful when writing a load test. Each entry
2037    /// must include a tag, which is an arbitrary string identifying the debug message.
2038    /// It may also optionally include references to the GooseRequestMetric made, the headers
2039    /// returned by the server, and the response body returned by the server,
2040    ///
2041    /// As the response body can be large, the `--no-debug-body` option (or
2042    /// [`GooseDefault::NoDebugBody`](../config/enum.GooseDefault.html#variant.NoDebugBody) default)
2043    /// can be set to prevent the debug log from including the response body. When this option
2044    /// is enabled, the body will always show up as `null` in the debug log.
2045    ///
2046    /// Calls to [`GooseUser::set_failure`] automatically invoke `log_debug`.
2047    ///
2048    /// To enable the debug log, a load test must be run with the `--debug-log-file=foo`
2049    /// option set, where `foo` is either a relative or an absolute path of the log file
2050    /// to create. Any existing file will be overwritten.
2051    ///
2052    /// In the following example, we are logging debug messages whenever there are errors.
2053    ///
2054    /// # Example
2055    /// ```rust
2056    /// use goose::prelude::*;
2057    ///
2058    /// let mut transaction = transaction!(loadtest_index_page);
2059    ///
2060    /// async fn loadtest_index_page(user: &mut GooseUser) -> TransactionResult {
2061    ///     let mut goose = user.get("").await?;
2062    ///
2063    ///     match goose.response {
2064    ///         Ok(response) => {
2065    ///             // Grab a copy of the headers so we can include them when logging errors.
2066    ///             let headers = &response.headers().clone();
2067    ///             // We only need to check pages that returned a success status code.
2068    ///             if !response.status().is_success() {
2069    ///                 match response.text().await {
2070    ///                     Ok(html) => {
2071    ///                         // Server returned an error code, log everything.
2072    ///                         user.log_debug(
2073    ///                             "error loading /",
2074    ///                             Some(&goose.request),
2075    ///                             Some(headers),
2076    ///                             Some(&html),
2077    ///                         );
2078    ///                     },
2079    ///                     Err(e) => {
2080    ///                         // No body was returned, log everything else.
2081    ///                         user.log_debug(
2082    ///                             &format!("error loading /: {}", e),
2083    ///                             Some(&goose.request),
2084    ///                             Some(headers),
2085    ///                             None,
2086    ///                         );
2087    ///                     }
2088    ///                 }
2089    ///             }
2090    ///         },
2091    ///         // No response from server.
2092    ///         Err(e) => {
2093    ///             user.log_debug(
2094    ///                 "no response from server when loading /",
2095    ///                 Some(&goose.request),
2096    ///                 None,
2097    ///                 None,
2098    ///             );
2099    ///         }
2100    ///     }
2101    ///
2102    ///     Ok(())
2103    /// }
2104    /// ````
2105    pub fn log_debug(
2106        &self,
2107        tag: &str,
2108        request: Option<&GooseRequestMetric>,
2109        headers: Option<&header::HeaderMap>,
2110        body: Option<&str>,
2111    ) -> TransactionResult {
2112        if !self.config.debug_log.is_empty() {
2113            // Logger is not defined when running
2114            // [`test_start`](../struct.GooseAttack.html#method.test_start),
2115            // [`test_stop`](../struct.GooseAttack.html#method.test_stop), and during testing.
2116            if let Some(logger) = self.logger.clone() {
2117                if self.config.no_debug_body {
2118                    if let Err(e) = logger.send(Some(GooseLog::Debug(GooseDebug::new(
2119                        tag, request, headers, None,
2120                    )))) {
2121                        return Err(Box::new(e.into()));
2122                    }
2123                } else if let Err(e) = logger.send(Some(GooseLog::Debug(GooseDebug::new(
2124                    tag, request, headers, body,
2125                )))) {
2126                    return Err(Box::new(e.into()));
2127                }
2128            }
2129        }
2130
2131        Ok(())
2132    }
2133
2134    /// Manually build a
2135    /// [`reqwest::Client`](https://docs.rs/reqwest/*/reqwest/struct.Client.html).
2136    ///
2137    /// By default, Goose configures the following options when building a
2138    /// [`reqwest::Client`](https://docs.rs/reqwest/*/reqwest/struct.Client.html):
2139    ///  - reports itself as the
2140    ///    [`user_agent`](https://docs.rs/reqwest/*/reqwest/struct.ClientBuilder.html#method.user_agent)
2141    ///    requesting web pages (ie `goose/0.15.2`);
2142    ///  - [stores cookies](https://docs.rs/reqwest/*/reqwest/struct.ClientBuilder.html#method.cookie_store),
2143    ///    generally necessary if you aim to simulate logged in users;
2144    ///  - enables
2145    ///    [`gzip`](https://docs.rs/reqwest/*/reqwest/struct.ClientBuilder.html#method.gzip) compression;
2146    ///  - sets a 60 second [`timeout`](https://docs.rs/reqwest/*/reqwest/struct.ClientBuilder.html#method.timeout) all
2147    ///    on all requests.
2148    ///
2149    /// # Default configuration:
2150    ///
2151    /// ```rust
2152    /// use reqwest::Client;
2153    /// use core::time::Duration;
2154    ///
2155    /// static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
2156    ///
2157    /// let builder = Client::builder()
2158    ///   .user_agent(APP_USER_AGENT)
2159    ///   .cookie_store(true)
2160    ///   .gzip(true)
2161    ///   .timeout(Duration::from_secs(60));
2162    /// ```
2163    ///
2164    /// Alternatively, you can use this function to manually build a
2165    /// [`reqwest::Client`](https://docs.rs/reqwest/*/reqwest/struct.Client.html).
2166    /// with custom configuration. Available options are found in the
2167    /// [`reqwest::ClientBuilder`](https://docs.rs/reqwest/*/reqwest/struct.ClientBuilder.html)
2168    /// documentation.
2169    ///
2170    /// When manually building a
2171    /// [`reqwest::Client`](https://docs.rs/reqwest/*/reqwest/struct.Client.html),
2172    /// there are a few things to be aware of:
2173    ///  - Manually building a client in [`test_start`](../struct.GooseAttack.html#method.test_start)
2174    ///    will only affect requests made during test setup;
2175    ///  - Manually building a client in [`test_stop`](../struct.GooseAttack.html#method.test_stop)
2176    ///    will only affect requests made during test teardown;
2177    ///  - A manually built client is specific to a single Goose thread -- if you are
2178    ///    generating a large load test with many users, each will need to manually build their
2179    ///    own client (typically you'd do this in a Transaction that is registered with
2180    ///    [`Transaction::set_on_start()`] in each Scenario requiring a custom client;
2181    ///  - Manually building a client will completely replace the automatically built client
2182    ///    with a brand new one, so any configuration, cookies or headers set in the previously
2183    ///    built client will be gone;
2184    ///  - You must include all desired configuration, as you are completely replacing Goose
2185    ///    defaults. For example, if you want Goose clients to store cookies, you will have to
2186    ///    include
2187    ///    [`.cookie_store(true)`](https://docs.rs/reqwest/*/reqwest/struct.ClientBuilder.html#method.cookie_store).
2188    ///
2189    /// In the following example, the Goose client is configured with a different user agent,
2190    /// sets a default header on every request, stores cookies, supports gzip compression, and
2191    /// times out requests after 30 seconds.
2192    ///
2193    /// ## Example
2194    /// ```rust
2195    /// use goose::prelude::*;
2196    /// use core::time::Duration;
2197    ///
2198    /// transaction!(setup_custom_client).set_on_start();
2199    ///
2200    /// async fn setup_custom_client(user: &mut GooseUser) -> TransactionResult {
2201    ///     use reqwest::{Client, header};
2202    ///
2203    ///     // Build a custom HeaderMap to include with all requests made by this client.
2204    ///     let mut headers = header::HeaderMap::new();
2205    ///     headers.insert("X-Custom-Header", header::HeaderValue::from_str("custom value").unwrap());
2206    ///
2207    ///     // Build a custom client.
2208    ///     let builder = Client::builder()
2209    ///         .default_headers(headers)
2210    ///         .user_agent("custom user agent")
2211    ///         .cookie_store(true)
2212    ///         .gzip(true)
2213    ///         .timeout(Duration::from_secs(30));
2214    ///
2215    ///     // Assign the custom client to this GooseUser.
2216    ///     user.set_client_builder(builder).await?;
2217    ///
2218    ///     Ok(())
2219    /// }
2220    /// ```
2221    ///
2222    /// # Alternative Compression Algorithms
2223    /// Reqwest also supports
2224    /// [`brotli`](https://docs.rs/reqwest/*/reqwest/struct.ClientBuilder.html#method.brotli) and
2225    /// [`deflate`](https://docs.rs/reqwest/*/reqwest/struct.ClientBuilder.html#method.deflate) compression.
2226    ///
2227    /// To enable either, you must enable the features in your load test's `Cargo.toml`, for example:
2228    /// ```text
2229    /// reqwest = { version = "^0.11.4",  default-features = false, features = [
2230    ///     "brotli",
2231    ///     "cookies",
2232    ///     "deflate",
2233    ///     "gzip",
2234    ///     "json",
2235    /// ] }
2236    /// ```
2237    ///
2238    /// Once enabled, you can add `.brotli(true)` and/or `.deflate(true)` to your custom
2239    /// [`reqwest::Client::builder()`], following the documentation above.
2240    ///
2241    /// # Custom Cookies
2242    /// Custom cookies can also be manually set when building a custom [`reqwest::Client`]. This requires
2243    /// loading the [`GooseUser::base_url`] being load tested in order to properly build the cookie. Then
2244    /// a custom [`reqwest::cookie::Jar`] is created and the custom cookie is added with
2245    /// [`reqwest::cookie::Jar::add_cookie_str`]. Finally, the new cookie jar must be specified as the
2246    /// [`reqwest::ClientBuilder::cookie_provider`] for the custom client.
2247    ///
2248    /// ## Example
2249    /// ```rust
2250    /// use reqwest::{cookie::Jar, Client};
2251    /// use std::sync::Arc;
2252    ///
2253    /// use goose::prelude::*;
2254    ///
2255    /// transaction!(custom_cookie_with_custom_client).set_on_start();
2256    ///
2257    /// async fn custom_cookie_with_custom_client(user: &mut GooseUser) -> TransactionResult {
2258    ///     // Prepare the contents of a custom cookie.
2259    ///     let cookie = "my-custom-cookie=custom-value";
2260    ///
2261    ///     // Pre-load one or more cookies into a custom cookie jar to use with this client.
2262    ///     let jar = Jar::default();
2263    ///     jar.add_cookie_str(
2264    ///         cookie,
2265    ///         &user.base_url,
2266    ///     );
2267    ///
2268    ///     // Build a custom client.
2269    ///     let builder = Client::builder()
2270    ///         .user_agent("example-loadtest")
2271    ///         .cookie_store(true)
2272    ///         .cookie_provider(Arc::new(jar))
2273    ///         .gzip(true);
2274    ///
2275    ///     // Assign the custom client to this GooseUser.
2276    ///     user.set_client_builder(builder).await?;
2277    ///
2278    ///     Ok(())
2279    /// }
2280    /// ```
2281    pub async fn set_client_builder(
2282        &mut self,
2283        builder: ClientBuilder,
2284    ) -> Result<(), TransactionError> {
2285        self.client = builder.build()?;
2286
2287        Ok(())
2288    }
2289
2290    /// Some websites use multiple domains to serve traffic, redirecting depending on
2291    /// the user's roll. For this reason, Goose needs to respect a redirect of the
2292    /// `base_url` and subsequent paths should be built from the redirect domain.
2293    ///
2294    /// For example, if the `base_url` (ie `--host`) is set to `foo.example.com` and the
2295    /// load test requests `/login`, thereby loading `http://foo.example.com/login` and
2296    /// this request gets redirected by the server to `http://foo-secure.example.com/`,
2297    /// subsequent requests made by this user need to be against the new
2298    /// `foo-secure.example.com domain`. (Further, if the `base_url` is again redirected,
2299    /// such as when loading `http://foo-secure.example.com/logout`, the user should
2300    /// again follow for subsequent requests, perhaps in this case back to
2301    /// `foo.example.com`.)
2302    ///
2303    /// Load tests can also request absolute URLs, and if these URLs are redirected
2304    /// it does not affect the `base_url` of the load test. For example, if
2305    /// `foo.example.com` is the base url, and the load test requests
2306    /// `http://bar.example.com` (a different domain) and this request gets redirected
2307    /// to `http://other.example.com`, subsequent relative requests would still be made
2308    /// against `foo.example.com`.
2309    ///
2310    /// This functionality is used internally by Goose to follow redirects of the
2311    /// `base_url` when `--sticky-follow` is specified at run time, or
2312    /// [`set_default`](../struct.GooseAttack.html#method.set_default)
2313    /// `(`[`GooseDefault::StickyFollow`](../config/enum.GooseDefault.html#variant.StickyFollow)
2314    /// `, true)` is enabled. It is also
2315    /// available to be manually invoked from a load test such as in the following
2316    /// example.
2317    ///
2318    /// # Example
2319    /// ```rust
2320    /// use goose::prelude::*;
2321    /// use std::time::Duration;
2322    ///
2323    /// #[tokio::main]
2324    /// async fn main() -> Result<(), GooseError> {
2325    ///     let _goose_metrics = GooseAttack::initialize()?
2326    ///         .register_scenario(scenario!("LoadtestTransactions")
2327    ///             .set_host("http://foo.example.com/")
2328    ///             .set_wait_time(Duration::from_secs(0), Duration::from_secs(3))?
2329    ///             .register_transaction(transaction!(transaction_foo).set_weight(10)?)
2330    ///             .register_transaction(transaction!(transaction_bar))
2331    ///         )
2332    ///         // Set a default run time so this test runs to completion.
2333    ///         .set_default(GooseDefault::RunTime, 1)?
2334    ///         .execute()
2335    ///         .await?;
2336    ///
2337    ///     Ok(())
2338    /// }
2339    ///
2340    /// async fn transaction_foo(user: &mut GooseUser) -> TransactionResult {
2341    ///     let _goose = user.get("").await?;
2342    ///
2343    ///     Ok(())
2344    /// }
2345    ///
2346    /// async fn transaction_bar(user: &mut GooseUser) -> TransactionResult {
2347    ///     // Before this transaction runs, all requests are being made against
2348    ///     // http://foo.example.com, after this transaction runs all subsequent
2349    ///     // requests are made against http://bar.example.com/.
2350    ///     user.set_base_url("http://bar.example.com/");
2351    ///     let _goose = user.get("").await?;
2352    ///
2353    ///     Ok(())
2354    /// }
2355    /// ```
2356    pub fn set_base_url(&mut self, host: &str) -> Result<(), Box<TransactionError>> {
2357        self.base_url = match Url::parse(host) {
2358            Ok(u) => u,
2359            Err(e) => return Err(Box::new(e.into())),
2360        };
2361        Ok(())
2362    }
2363}
2364
2365/// Internal helper function to create the default GooseUser reqwest client
2366pub(crate) fn create_reqwest_client(
2367    configuration: &GooseConfiguration,
2368) -> Result<Client, reqwest::Error> {
2369    // Either use manually configured timeout, or default.
2370    let timeout = if configuration.timeout.is_some() {
2371        match crate::util::get_float_from_string(configuration.timeout.clone()) {
2372            Some(f) => f as u64 * 1_000,
2373            None => GOOSE_REQUEST_TIMEOUT,
2374        }
2375    } else {
2376        GOOSE_REQUEST_TIMEOUT
2377    };
2378
2379    Client::builder()
2380        .user_agent(APP_USER_AGENT)
2381        .cookie_store(true)
2382        .timeout(Duration::from_millis(timeout))
2383        // Enable gzip unless `--no-gzip` flag is enabled.
2384        .gzip(!configuration.no_gzip)
2385        // Validate https certificates unless `--accept-invalid-certs` is enabled.
2386        .danger_accept_invalid_certs(configuration.accept_invalid_certs)
2387        .build()
2388}
2389
2390/// Defines the HTTP requests that Goose makes.
2391///
2392/// Can be manually created and configured with [`GooseRequest::builder`], but it's typically
2393/// more convenient to use the [`GooseUser::get`], [`GooseUser::get_named`], [`GooseUser::post`],
2394/// [`GooseUser::post_form`], [`GooseUser::post_json`], [`GooseUser::head`] and
2395/// [`GooseUser::delete`] helpers.
2396///
2397/// For complete instructions review [`GooseRequestBuilder`].
2398#[derive(Debug)]
2399pub struct GooseRequest<'a> {
2400    // Defaults to `""`.
2401    path: &'a str,
2402    // Defaults to [`GooseMethod::Get`].
2403    method: GooseMethod,
2404    // Defaults to [`None`].
2405    name: Option<&'a str>,
2406    // Defaults to [`None`].
2407    expect_status_code: Option<u16>,
2408    // Defaults to [`false`].
2409    error_on_fail: bool,
2410    // Defaults to [`None`].
2411    request_builder: Option<RequestBuilder>,
2412}
2413impl<'a> GooseRequest<'a> {
2414    /// Convenience function to bring [`GooseRequestBuilder`] into scope.
2415    pub fn builder() -> GooseRequestBuilder<'a> {
2416        GooseRequestBuilder::new()
2417    }
2418}
2419
2420/// Used to build a [`GooseRequest`] object, necessary to make a request with Goose.
2421///
2422/// It's only necessary to build manually if the [`GooseUser::get`], [`GooseUser::get_named`],
2423/// [`GooseUser::post`], [`GooseUser::post_form`], [`GooseUser::post_json`], [`GooseUser::head`]
2424/// and [`GooseUser::delete`] helpers don't provide you with enough flexibility.
2425///
2426/// # Example
2427/// ```rust
2428/// use goose::prelude::*;
2429///
2430/// let mut a_transaction = transaction!(transaction_function);
2431///
2432/// // A simple transaction that loads a relative path.
2433/// async fn transaction_function(user: &mut GooseUser) -> TransactionResult {
2434///     // Manually create a GooseRequestBuilder object.
2435///     let goose_request = GooseRequest::builder()
2436///         // Set a relative path to request.
2437///         .path("about")
2438///         // Name the request in the metircs.
2439///         .name("About page")
2440///         // Build the GooseRequest object.
2441///         .build();
2442///
2443///     // Make the configured request.
2444///     let _goose = user.request(goose_request).await?;
2445///
2446///     Ok(())
2447/// }
2448/// ```
2449pub struct GooseRequestBuilder<'a> {
2450    path: &'a str,
2451    method: GooseMethod,
2452    name: Option<&'a str>,
2453    expect_status_code: Option<u16>,
2454    error_on_fail: bool,
2455    request_builder: Option<RequestBuilder>,
2456}
2457impl<'a> GooseRequestBuilder<'a> {
2458    // Internal method to build a [`GooseRequest`] from a [`GooseRequestBuilder`].
2459    fn new() -> Self {
2460        Self {
2461            path: "",
2462            method: GooseMethod::Get,
2463            name: None,
2464            expect_status_code: None,
2465            error_on_fail: false,
2466            request_builder: None,
2467        }
2468    }
2469
2470    /// Set the path to request.
2471    ///
2472    /// Typically is a relative path allowing Goose to append a configurable base_url.
2473    ///
2474    /// Defaults to `""` (the main index).
2475    ///
2476    /// # Example
2477    /// This can be implemented in a simpler way using the [`GooseUser::get`] helper function.
2478    /// ```rust
2479    /// use goose::prelude::*;
2480    ///
2481    /// let mut a_transaction = transaction!(transaction_function);
2482    ///
2483    /// // A simple transaction that loads a relative path.
2484    /// async fn transaction_function(user: &mut GooseUser) -> TransactionResult {
2485    ///     // Manually create a GooseRequestBuilder object.
2486    ///     let goose_request = GooseRequest::builder()
2487    ///         // Set a relative path to request.
2488    ///         .path("a/relative/path")
2489    ///         // Build the GooseRequest object.
2490    ///         .build();
2491    ///
2492    ///     // Make the configured request.
2493    ///     let _goose = user.request(goose_request).await?;
2494    ///
2495    ///     Ok(())
2496    /// }
2497    /// ```
2498    pub fn path(mut self, path: impl Into<&'a str>) -> Self {
2499        self.path = path.into();
2500        self
2501    }
2502
2503    /// Set the method of the request.
2504    ///
2505    /// Must be set to a [`GooseMethod`].
2506    ///
2507    /// Defaults to [`GooseMethod::Get`].
2508    ///
2509    /// # Example
2510    /// ```rust
2511    /// use goose::prelude::*;
2512    ///
2513    /// let mut a_transaction = transaction!(transaction_function);
2514    ///
2515    /// // Make a DELETE request.
2516    /// async fn transaction_function(user: &mut GooseUser) -> TransactionResult {
2517    ///     // Manually create a GooseRequestBuilder object.
2518    ///     let goose_request = GooseRequest::builder()
2519    ///         // Set a relative path to request.
2520    ///         .path("path/to/delete")
2521    ///         // Set the method to DELETE.
2522    ///         .method(GooseMethod::Delete)
2523    ///         // Build the GooseRequest object.
2524    ///         .build();
2525    ///
2526    ///     // Make the configured DELETE request.
2527    ///     let _goose = user.request(goose_request).await?;
2528    ///
2529    ///     Ok(())
2530    /// }
2531    /// ```
2532    pub fn method(mut self, method: GooseMethod) -> Self {
2533        self.method = method;
2534        self
2535    }
2536
2537    /// Set a name for the request, affecting how it shows up in metrics.
2538    ///
2539    /// Must be set to a [`GooseMethod`].
2540    ///
2541    /// Defaults to [`GooseMethod::Get`].
2542    ///
2543    /// # Example
2544    /// ```rust
2545    /// use goose::prelude::*;
2546    ///
2547    /// let mut a_transaction = transaction!(transaction_function);
2548    ///
2549    /// // Make a named request.
2550    /// async fn transaction_function(user: &mut GooseUser) -> TransactionResult {
2551    ///     // Manually create a GooseRequestBuilder object.
2552    ///     let goose_request = GooseRequest::builder()
2553    ///         // Set a relative path to request.
2554    ///         .path("path/to/request")
2555    ///         // Name the request in the metrics.
2556    ///         .name("custom name")
2557    ///         // Build the GooseRequest object.
2558    ///         .build();
2559    ///
2560    ///     // Make the configured request.
2561    ///     let _goose = user.request(goose_request).await?;
2562    ///
2563    ///     Ok(())
2564    /// }
2565    /// ```
2566    pub fn name(mut self, name: impl Into<&'a str>) -> Self {
2567        self.name = Some(name.into());
2568        self
2569    }
2570
2571    /// Manually configure the expected HTTP response status code.
2572    ///
2573    /// Defaults to [`reqwest::StatusCode::is_success`].
2574    ///
2575    /// # Example
2576    /// Intentionally request a 404 page, and do not trigger an error.
2577    /// ```rust
2578    /// use goose::prelude::*;
2579    ///
2580    /// let mut a_transaction = transaction!(transaction_function);
2581    ///
2582    /// // Make a named request.
2583    /// async fn transaction_function(user: &mut GooseUser) -> TransactionResult {
2584    ///     // Manually create a GooseRequestBuilder object.
2585    ///     let goose_request = GooseRequest::builder()
2586    ///         // Set a relative path to request.
2587    ///         .path("no/such/path")
2588    ///         // Tell Goose to expect a 404 HTTP response status code.
2589    ///         .expect_status_code(404)
2590    ///         // Build the GooseRequest object.
2591    ///         .build();
2592    ///
2593    ///     // Make the configured request.
2594    ///     let _goose = user.request(goose_request).await?;
2595    ///
2596    ///     Ok(())
2597    /// }
2598    /// ```
2599    pub fn expect_status_code(mut self, status_code: u16) -> Self {
2600        self.expect_status_code = Some(status_code);
2601        self
2602    }
2603
2604    /// Configure whether the request should return on error when it
2605    /// fails
2606    ///
2607    /// Defaults to [`false`].
2608    ///
2609    /// # Example
2610    /// Intentionally request a 404 page, and do not trigger an error.
2611    /// ```rust
2612    /// use goose::prelude::*;
2613    ///
2614    /// let mut a_transaction = transaction!(transaction_function);
2615    ///
2616    /// // Make a named request.
2617    /// async fn transaction_function(user: &mut GooseUser) -> TransactionResult {
2618    ///     // Manually create a GooseRequestBuilder object.
2619    ///     let goose_request = GooseRequest::builder()
2620    ///         // Set a relative path to request.
2621    ///         .path("no/such/path")
2622    ///         // Tell Goose to expect a 404 HTTP response status code.
2623    ///         .expect_status_code(404)
2624    ///         // Tell Goose to return an error if the status code is
2625    ///         // not a 404
2626    ///         .error_on_fail()
2627    ///         // Build the GooseRequest object.
2628    ///         .build();
2629    ///
2630    ///     // Make the configured request.
2631    ///     let _goose = user.request(goose_request).await?;
2632    ///
2633    ///     Ok(())
2634    /// }
2635    /// ```
2636    pub fn error_on_fail(mut self) -> Self {
2637        self.error_on_fail = true;
2638        self
2639    }
2640
2641    /// Manually create the [`reqwest::RequestBuilder`] used to make a request.
2642    ///
2643    /// # Example
2644    /// Manually create a `RequestBuilder` in order to set a timeout.
2645    /// ```rust
2646    /// use goose::prelude::*;
2647    ///
2648    /// let mut a_transaction = transaction!(transaction_function);
2649    ///
2650    /// async fn transaction_function(user: &mut GooseUser) -> TransactionResult {
2651    ///     use std::time::Duration;
2652    ///
2653    ///     // Manually interact with the Reqwest RequestBuilder object.
2654    ///     let request_builder = user.get_request_builder(&GooseMethod::Get, "no/such/path")?
2655    ///         // Configure the request to timeout if it takes longer than 500 milliseconds.
2656    ///         .timeout(Duration::from_millis(500));
2657    ///
2658    ///     // Manually build a GooseRequest in order to set our custom RequestBuilder.
2659    ///     let goose_request = GooseRequest::builder()
2660    ///         // Manually add our custom RequestBuilder object.
2661    ///         .set_request_builder(request_builder)
2662    ///         // Turn the GooseRequestBuilder object into a GooseRequest.
2663    ///         .build();
2664    ///
2665    ///     // Finally make the actual request with our custom GooseRequest object.
2666    ///     let _goose = user.request(goose_request).await?;
2667    ///
2668    ///     Ok(())
2669    /// }
2670    /// ```
2671    pub fn set_request_builder(mut self, request_builder: RequestBuilder) -> Self {
2672        self.request_builder = Some(request_builder);
2673        self
2674    }
2675
2676    /// Build the [`GooseRequest`] object which is then passed to [`GooseUser::request`].
2677    ///
2678    /// # Example
2679    /// ```rust
2680    /// use goose::prelude::*;
2681    ///
2682    /// // Build the default "GET /".
2683    /// let goose_request = GooseRequest::builder().build();
2684    /// ```
2685    pub fn build(self) -> GooseRequest<'a> {
2686        let Self {
2687            path,
2688            method,
2689            name,
2690            expect_status_code,
2691            error_on_fail,
2692            request_builder,
2693        } = self;
2694        GooseRequest {
2695            path,
2696            method,
2697            name,
2698            expect_status_code,
2699            error_on_fail,
2700            request_builder,
2701        }
2702    }
2703}
2704
2705/// Remove path from Reqwest error to avoid having a lot of distincts error
2706/// when path parameters are used.
2707fn clean_reqwest_error(e: &reqwest::Error, request_name: &str) -> String {
2708    let kind = if e.is_builder() {
2709        "builder error"
2710    } else if e.is_request() {
2711        "error sending request"
2712    } else if e.is_body() {
2713        "request or response body error"
2714    } else if e.is_decode() {
2715        "error decoding response body"
2716    } else if e.is_redirect() {
2717        "error following redirect"
2718    } else {
2719        "Http status"
2720    };
2721
2722    if let Some(ref e) = std::error::Error::source(e) {
2723        format!("{} {}: {}", kind, request_name, e)
2724    } else {
2725        format!("{} {}", kind, request_name)
2726    }
2727}
2728
2729/// A helper to determine which host should be prepended to relative load test
2730/// paths in this Scenario.
2731///
2732/// The first of these defined will be returned as the prepended host:
2733///  1. `--host` (host specified on the command line when running load test)
2734///  2. [`Scenario`](./struct.Scenario.html)`.host` (default host defined
2735///     for the current scenario)
2736///  3. [`GooseDefault::Host`](../config/enum.GooseDefault.html#variant.Host) (default
2737///     host defined for the current load test)
2738pub fn get_base_url(
2739    config_host: Option<String>,
2740    scenario_host: Option<String>,
2741    default_host: Option<String>,
2742) -> Result<Url, GooseError> {
2743    // If the `--host` CLI option is set, build the URL with it.
2744    match config_host {
2745        Some(host) => Ok(
2746            Url::parse(&host).map_err(|parse_error| GooseError::InvalidHost {
2747                host,
2748                detail: "There was a failure parsing the host specified with --host.".to_string(),
2749                parse_error,
2750            })?,
2751        ),
2752        None => {
2753            match scenario_host {
2754                // Otherwise, if `Scenario.host` is defined, usee this
2755                Some(host) => {
2756                    Ok(
2757                        Url::parse(&host).map_err(|parse_error| GooseError::InvalidHost {
2758                            host,
2759                            detail: "There was a failure parsing the host specified with the Scenario.set_host() function.".to_string(),
2760                            parse_error,
2761                        })?,
2762                    )
2763                }
2764                // Otherwise, use global `GooseAttack.host`. `unwrap` okay as host validation was done at startup.
2765                None => {
2766                    // Host is required, if we get here it's safe to unwrap this variable.
2767                    let default_host = default_host.unwrap();
2768                    Ok(
2769                        Url::parse(&default_host).map_err(|parse_error| GooseError::InvalidHost {
2770                            host: default_host.to_string(),
2771                            detail: "There was a failure parsing the host specified globally with the GooseAttack.set_default() function.".to_string(),
2772                            parse_error,
2773                        })?,
2774                    )
2775                }
2776            }
2777        }
2778    }
2779}
2780
2781/// The function type of a goose transaction function.
2782pub type TransactionFunction = Arc<
2783    dyn for<'r> Fn(
2784            &'r mut GooseUser,
2785        ) -> Pin<Box<dyn Future<Output = TransactionResult> + Send + 'r>>
2786        + Send
2787        + Sync,
2788>;
2789
2790/// An individual transaction within a [`Scenario`](./struct.Scenario.html).
2791#[derive(Clone)]
2792pub struct Transaction {
2793    /// An index into [`Scenario`](./struct.Scenario.html)`.transaction`, indicating which
2794    /// transaction this is.
2795    pub transactions_index: usize,
2796    /// An optional name for the transaction, used when displaying metrics.
2797    pub name: String,
2798    /// An integer value that controls the frequency that this transaction will be run.
2799    pub weight: usize,
2800    /// An integer value that controls when this transaction runs compared to other transactions in the same
2801    /// [`Scenario`](./struct.Scenario.html).
2802    pub sequence: usize,
2803    /// A flag indicating that this transaction runs when the user starts.
2804    pub on_start: bool,
2805    /// A flag indicating that this transaction runs when the user stops.
2806    pub on_stop: bool,
2807    /// A required function that is executed each time this transaction runs.
2808    pub function: TransactionFunction,
2809}
2810impl Transaction {
2811    pub fn new(function: TransactionFunction) -> Self {
2812        trace!("new transaction");
2813        Transaction {
2814            transactions_index: usize::MAX,
2815            name: "".to_string(),
2816            weight: 1,
2817            sequence: 0,
2818            on_start: false,
2819            on_stop: false,
2820            function,
2821        }
2822    }
2823
2824    /// Set an optional name for the transaction, used when displaying metrics.
2825    ///
2826    /// Individual requests can also be named using [`GooseRequestBuilder`], or for GET
2827    /// requests with the [`GooseUser::get_named`] helper.
2828    ///
2829    /// # Example
2830    /// ```rust
2831    /// use goose::prelude::*;
2832    ///
2833    /// transaction!(my_transaction_function).set_name("foo");
2834    ///
2835    /// async fn my_transaction_function(user: &mut GooseUser) -> TransactionResult {
2836    ///     let _goose = user.get("").await?;
2837    ///
2838    ///     Ok(())
2839    /// }
2840    /// ```
2841    pub fn set_name(mut self, name: &str) -> Self {
2842        trace!("[{}] set_name: {}", self.transactions_index, self.name);
2843        self.name = name.to_string();
2844        self
2845    }
2846
2847    /// Set an optional flag indicating that this transaction should be run when
2848    /// a user first starts. This could be used to log the user in, and
2849    /// so all subsequent transaction are done as a logged in user. A transaction
2850    /// with this flag set will only run at start time (and optionally at stop
2851    /// time as well, if that flag is also set).
2852    ///
2853    /// On-start transactions can be sequenced and weighted. Sequences allow
2854    /// multiple on-start transactions to run in a controlled order. Weights allow
2855    /// on-start transactions to run multiple times when a user starts.
2856    ///
2857    /// # Example
2858    /// ```rust
2859    /// use goose::prelude::*;
2860    ///
2861    /// transaction!(my_on_start_function).set_on_start();
2862    ///
2863    /// async fn my_on_start_function(user: &mut GooseUser) -> TransactionResult {
2864    ///     let _goose = user.get("").await?;
2865    ///
2866    ///     Ok(())
2867    /// }
2868    /// ```
2869    pub fn set_on_start(mut self) -> Self {
2870        trace!(
2871            "{} [{}] set_on_start transaction",
2872            self.name,
2873            self.transactions_index
2874        );
2875        self.on_start = true;
2876        self
2877    }
2878
2879    /// Set an optional flag indicating that this transaction should be run when
2880    /// a user stops. This could be used to log a user out when the user
2881    /// finishes its load test. A transaction with this flag set will only run at
2882    /// stop time (and optionally at start time as well, if that flag is
2883    /// also set).
2884    ///
2885    /// On-stop transactions can be sequenced and weighted. Sequences allow
2886    /// multiple on-stop transactions to run in a controlled order. Weights allow
2887    /// on-stop transactions to run multiple times when a user stops.
2888    ///
2889    /// # Example
2890    /// ```rust
2891    /// use goose::prelude::*;
2892    ///
2893    /// transaction!(my_on_stop_function).set_on_stop();
2894    ///
2895    /// async fn my_on_stop_function(user: &mut GooseUser) -> TransactionResult {
2896    ///     let _goose = user.get("").await?;
2897    ///
2898    ///     Ok(())
2899    /// }
2900    /// ```
2901    pub fn set_on_stop(mut self) -> Self {
2902        trace!(
2903            "{} [{}] set_on_stop transaction",
2904            self.name,
2905            self.transactions_index
2906        );
2907        self.on_stop = true;
2908        self
2909    }
2910
2911    /// Sets a weight on an individual transaction. The larger the value of weight, the more often it will be run
2912    /// in the Scenario. For example, if one transaction has a weight of 3 and another transaction has a weight of
2913    /// 1, the first transaction will run 3 times as often.
2914    ///
2915    /// # Example
2916    /// ```rust
2917    /// use goose::prelude::*;
2918    ///
2919    /// #[tokio::main]
2920    /// async fn main() -> Result<(), GooseError> {
2921    ///     transaction!(transaction_function).set_weight(3)?;
2922    ///
2923    ///     Ok(())
2924    /// }
2925    ///
2926    /// async fn transaction_function(user: &mut GooseUser) -> TransactionResult {
2927    ///     let _goose = user.get("").await?;
2928    ///
2929    ///     Ok(())
2930    /// }
2931    /// ```
2932    pub fn set_weight(mut self, weight: usize) -> Result<Self, GooseError> {
2933        trace!(
2934            "{} [{}] set_weight: {}",
2935            self.name,
2936            self.transactions_index,
2937            weight
2938        );
2939        if weight == 0 {
2940            return Err(GooseError::InvalidWeight {
2941                weight,
2942                detail: "Weight must be set to at least 1.".to_string(),
2943            });
2944        }
2945        self.weight = weight;
2946
2947        Ok(self)
2948    }
2949
2950    /// Defines the sequence value of an individual transactions. Transactions are run in order of their
2951    /// sequence value, so a transaction with a sequence value of 1 will run before a transaction with a
2952    /// sequence value of 2. Transactions with no sequence value (or a sequence value of 0) will run last,
2953    /// after all transactions with positive sequence values.
2954    ///
2955    /// All transactions with the same sequence value will run in a random order. Transactions can be
2956    /// assigned both squence values and weights.
2957    ///
2958    /// # Examples
2959    /// In this first example, the variable names indicate the order the transactions will be run in:
2960    /// ```rust
2961    /// use goose::prelude::*;
2962    ///
2963    /// let runs_first = transaction!(first_transaction_function).set_sequence(3);
2964    /// let runs_second = transaction!(second_transaction_function).set_sequence(5835);
2965    /// let runs_last = transaction!(third_transaction_function);
2966    ///
2967    /// async fn first_transaction_function(user: &mut GooseUser) -> TransactionResult {
2968    ///     let _goose = user.get("1").await?;
2969    ///
2970    ///     Ok(())
2971    /// }
2972    ///
2973    /// async fn second_transaction_function(user: &mut GooseUser) -> TransactionResult {
2974    ///     let _goose = user.get("2").await?;
2975    ///
2976    ///     Ok(())
2977    /// }
2978    ///
2979    /// async fn third_transaction_function(user: &mut GooseUser) -> TransactionResult {
2980    ///     let _goose = user.get("3").await?;
2981    ///
2982    ///     Ok(())
2983    /// }
2984    /// ```
2985    ///
2986    /// In the following example, the `runs_first` transactions runs two times, then one instance of `runs_second`
2987    /// and two instances of `also_runs_second` are all three run. The user will do this over and over
2988    /// the entire time it runs, with `runs_first` always running first, then the other transactions being
2989    /// run in a random and weighted order:
2990    /// ```rust
2991    /// use goose::prelude::*;
2992    ///
2993    /// #[tokio::main]
2994    /// async fn main() -> Result<(), GooseError> {
2995    ///     let runs_first = transaction!(first_transaction_function).set_sequence(1).set_weight(2)?;
2996    ///     let runs_second = transaction!(second_transaction_function_a).set_sequence(2);
2997    ///     let also_runs_second = transaction!(second_transaction_function_b).set_sequence(2).set_weight(2)?;
2998    ///
2999    ///     Ok(())
3000    /// }
3001    ///
3002    /// async fn first_transaction_function(user: &mut GooseUser) -> TransactionResult {
3003    ///     let _goose = user.get("1").await?;
3004    ///
3005    ///     Ok(())
3006    /// }
3007    ///
3008    /// async fn second_transaction_function_a(user: &mut GooseUser) -> TransactionResult {
3009    ///     let _goose = user.get("2a").await?;
3010    ///
3011    ///     Ok(())
3012    /// }
3013    ///
3014    ///     async fn second_transaction_function_b(user: &mut GooseUser) -> TransactionResult {
3015    ///       let _goose = user.get("2b").await?;
3016    ///
3017    ///       Ok(())
3018    ///     }
3019    /// ```
3020    pub fn set_sequence(mut self, sequence: usize) -> Self {
3021        trace!(
3022            "{} [{}] set_sequence: {}",
3023            self.name,
3024            self.transactions_index,
3025            sequence
3026        );
3027        if sequence < 1 {
3028            info!(
3029                "setting sequence to 0 for transaction {} is unnecessary, sequence disabled",
3030                self.name
3031            );
3032        }
3033        self.sequence = sequence;
3034        self
3035    }
3036}
3037impl Hash for Transaction {
3038    fn hash<H: Hasher>(&self, state: &mut H) {
3039        self.transactions_index.hash(state);
3040        self.name.hash(state);
3041        self.weight.hash(state);
3042        self.sequence.hash(state);
3043        self.on_start.hash(state);
3044        self.on_stop.hash(state);
3045    }
3046}
3047
3048#[cfg(test)]
3049mod tests {
3050    use super::*;
3051
3052    use gumdrop::Options;
3053    use httpmock::{
3054        Method::{GET, POST},
3055        MockServer,
3056    };
3057
3058    const EMPTY_ARGS: Vec<&str> = vec![];
3059
3060    fn setup_user(server: &MockServer) -> Result<GooseUser, GooseError> {
3061        let mut configuration = GooseConfiguration::parse_args_default(&EMPTY_ARGS).unwrap();
3062        configuration.co_mitigation = Some(GooseCoordinatedOmissionMitigation::Average);
3063        let base_url = get_base_url(Some(server.url("/")), None, None).unwrap();
3064        GooseUser::single(base_url, &configuration)
3065    }
3066
3067    #[test]
3068    fn goose_scenario() {
3069        // Simplistic test transaction functions.
3070        async fn test_function_a(user: &mut GooseUser) -> TransactionResult {
3071            let _goose = user.get("/a/").await?;
3072
3073            Ok(())
3074        }
3075
3076        async fn test_function_b(user: &mut GooseUser) -> TransactionResult {
3077            let _goose = user.get("/b/").await?;
3078
3079            Ok(())
3080        }
3081
3082        let mut scenario = scenario!("foo");
3083        assert_eq!(scenario.name, "foo");
3084        assert_eq!(scenario.scenarios_index, usize::MAX);
3085        assert_eq!(scenario.weight, 1);
3086        assert_eq!(scenario.transaction_wait, None);
3087        assert!(scenario.host.is_none());
3088        assert_eq!(scenario.transactions.len(), 0);
3089        assert_eq!(scenario.weighted_transactions.len(), 0);
3090        assert_eq!(scenario.weighted_on_start_transactions.len(), 0);
3091        assert_eq!(scenario.weighted_on_stop_transactions.len(), 0);
3092
3093        // Registering a transaction adds it to transactions, but doesn't update weighted_transactions.
3094        scenario = scenario.register_transaction(transaction!(test_function_a));
3095        assert_eq!(scenario.transactions.len(), 1);
3096        assert_eq!(scenario.weighted_transactions.len(), 0);
3097        assert_eq!(scenario.scenarios_index, usize::MAX);
3098        assert_eq!(scenario.weight, 1);
3099        assert_eq!(scenario.transaction_wait, None);
3100        assert!(scenario.host.is_none());
3101
3102        // Different transactions can be registered.
3103        scenario = scenario.register_transaction(transaction!(test_function_b));
3104        assert_eq!(scenario.transactions.len(), 2);
3105        assert_eq!(scenario.weighted_transactions.len(), 0);
3106        assert_eq!(scenario.scenarios_index, usize::MAX);
3107        assert_eq!(scenario.weight, 1);
3108        assert_eq!(scenario.transaction_wait, None);
3109        assert!(scenario.host.is_none());
3110
3111        // Same transactions can be registered again.
3112        scenario = scenario.register_transaction(transaction!(test_function_a));
3113        assert_eq!(scenario.transactions.len(), 3);
3114        assert_eq!(scenario.weighted_transactions.len(), 0);
3115        assert_eq!(scenario.scenarios_index, usize::MAX);
3116        assert_eq!(scenario.weight, 1);
3117        assert_eq!(scenario.transaction_wait, None);
3118        assert!(scenario.host.is_none());
3119
3120        // Setting weight only affects weight field.
3121        scenario = scenario.set_weight(50).unwrap();
3122        assert_eq!(scenario.weight, 50);
3123        assert_eq!(scenario.transactions.len(), 3);
3124        assert_eq!(scenario.weighted_transactions.len(), 0);
3125        assert_eq!(scenario.scenarios_index, usize::MAX);
3126        assert_eq!(scenario.transaction_wait, None);
3127        assert!(scenario.host.is_none());
3128
3129        // Weight can be changed.
3130        scenario = scenario.set_weight(5).unwrap();
3131        assert_eq!(scenario.weight, 5);
3132
3133        // Setting host only affects host field.
3134        scenario = scenario.set_host("http://foo.example.com/");
3135        assert_eq!(scenario.host, Some("http://foo.example.com/".to_string()));
3136        assert_eq!(scenario.weight, 5);
3137        assert_eq!(scenario.transactions.len(), 3);
3138        assert_eq!(scenario.weighted_transactions.len(), 0);
3139        assert_eq!(scenario.scenarios_index, usize::MAX);
3140        assert_eq!(scenario.transaction_wait, None);
3141
3142        // Host field can be changed.
3143        scenario = scenario.set_host("https://bar.example.com/");
3144        assert_eq!(scenario.host, Some("https://bar.example.com/".to_string()));
3145
3146        // Wait time only affects wait time fields.
3147        scenario = scenario
3148            .set_wait_time(Duration::from_secs(1), Duration::from_secs(10))
3149            .unwrap();
3150        assert_eq!(
3151            scenario.transaction_wait,
3152            Some((Duration::from_secs(1), Duration::from_secs(10)))
3153        );
3154        assert_eq!(scenario.host, Some("https://bar.example.com/".to_string()));
3155        assert_eq!(scenario.weight, 5);
3156        assert_eq!(scenario.transactions.len(), 3);
3157        assert_eq!(scenario.weighted_transactions.len(), 0);
3158        assert_eq!(scenario.scenarios_index, usize::MAX);
3159
3160        // Wait time can be changed.
3161        scenario = scenario
3162            .set_wait_time(Duration::from_secs(3), Duration::from_secs(9))
3163            .unwrap();
3164        assert_eq!(
3165            scenario.transaction_wait,
3166            Some((Duration::from_secs(3), Duration::from_secs(9)))
3167        );
3168    }
3169
3170    #[test]
3171    fn goose_transaction() {
3172        // Simplistic test transaction functions.
3173        async fn test_function_a(user: &mut GooseUser) -> TransactionResult {
3174            let _goose = user.get("/a/").await?;
3175
3176            Ok(())
3177        }
3178
3179        // Initialize scenario.
3180        let mut transaction = transaction!(test_function_a);
3181        assert_eq!(transaction.transactions_index, usize::MAX);
3182        assert_eq!(transaction.name, "".to_string());
3183        assert_eq!(transaction.weight, 1);
3184        assert_eq!(transaction.sequence, 0);
3185        assert!(!transaction.on_start);
3186        assert!(!transaction.on_stop);
3187
3188        // Name can be set, without affecting other fields.
3189        transaction = transaction.set_name("foo");
3190        assert_eq!(transaction.name, "foo".to_string());
3191        assert_eq!(transaction.weight, 1);
3192        assert_eq!(transaction.sequence, 0);
3193        assert!(!transaction.on_start);
3194        assert!(!transaction.on_stop);
3195
3196        // Name can be set multiple times.
3197        transaction = transaction.set_name("bar");
3198        assert_eq!(transaction.name, "bar".to_string());
3199
3200        // On start flag can be set, without affecting other fields.
3201        transaction = transaction.set_on_start();
3202        assert!(transaction.on_start);
3203        assert_eq!(transaction.name, "bar".to_string());
3204        assert_eq!(transaction.weight, 1);
3205        assert_eq!(transaction.sequence, 0);
3206        assert!(!transaction.on_stop);
3207
3208        // Setting on start flag twice doesn't change anything.
3209        transaction = transaction.set_on_start();
3210        assert!(transaction.on_start);
3211
3212        // On stop flag can be set, without affecting other fields.
3213        // It's possible to set both on_start and on_stop for same transaction.
3214        transaction = transaction.set_on_stop();
3215        assert!(transaction.on_stop);
3216        assert!(transaction.on_start);
3217        assert_eq!(transaction.name, "bar".to_string());
3218        assert_eq!(transaction.weight, 1);
3219        assert_eq!(transaction.sequence, 0);
3220
3221        // Setting on stop flag twice doesn't change anything.
3222        transaction = transaction.set_on_stop();
3223        assert!(transaction.on_stop);
3224
3225        // Setting weight doesn't change anything else.
3226        transaction = transaction.set_weight(2).unwrap();
3227        assert_eq!(transaction.weight, 2);
3228        assert!(transaction.on_stop);
3229        assert!(transaction.on_start);
3230        assert_eq!(transaction.name, "bar".to_string());
3231        assert_eq!(transaction.sequence, 0);
3232
3233        // Weight field can be changed multiple times.
3234        transaction = transaction.set_weight(3).unwrap();
3235        assert_eq!(transaction.weight, 3);
3236
3237        // Setting sequence doesn't change anything else.
3238        transaction = transaction.set_sequence(4);
3239        assert_eq!(transaction.sequence, 4);
3240        assert_eq!(transaction.weight, 3);
3241        assert!(transaction.on_stop);
3242        assert!(transaction.on_start);
3243        assert_eq!(transaction.name, "bar".to_string());
3244
3245        // Sequence field can be changed multiple times.
3246        transaction = transaction.set_sequence(8);
3247        assert_eq!(transaction.sequence, 8);
3248    }
3249
3250    #[tokio::test]
3251    async fn goose_user() {
3252        const HOST: &str = "http://example.com/";
3253        let configuration = GooseConfiguration::parse_args_default(&EMPTY_ARGS).unwrap();
3254        let base_url = get_base_url(Some(HOST.to_string()), None, None).unwrap();
3255        let user = GooseUser::new(0, "".to_string(), base_url, &configuration, 0, None).unwrap();
3256        assert_eq!(user.scenarios_index, 0);
3257        assert_eq!(user.weighted_users_index, usize::MAX);
3258
3259        // Confirm the URLs are correctly built using the default_host.
3260        let url = user.build_url("/foo").unwrap();
3261        assert_eq!(&url, &[HOST, "foo"].concat());
3262        let url = user.build_url("bar/").unwrap();
3263        assert_eq!(&url, &[HOST, "bar/"].concat());
3264        let url = user.build_url("/foo/bar").unwrap();
3265        assert_eq!(&url, &[HOST, "foo/bar"].concat());
3266
3267        // Confirm the URLs are built with their own specified host.
3268        let url = user.build_url("https://example.com/foo").unwrap();
3269        assert_eq!(url, "https://example.com/foo");
3270        let url = user
3271            .build_url("https://www.example.com/path/to/resource")
3272            .unwrap();
3273        assert_eq!(url, "https://www.example.com/path/to/resource");
3274
3275        // Create a second user, this time setting a scenario_host.
3276        let base_url = get_base_url(
3277            None,
3278            Some("http://www2.example.com/".to_string()),
3279            Some("http://www.example.com/".to_string()),
3280        )
3281        .unwrap();
3282        let user2 = GooseUser::new(0, "".to_string(), base_url, &configuration, 0, None).unwrap();
3283
3284        // Confirm the URLs are correctly built using the scenario_host.
3285        let url = user2.build_url("/foo").unwrap();
3286        assert_eq!(url, "http://www2.example.com/foo");
3287
3288        // Confirm URLs are still built with their own specified host.
3289        let url = user2.build_url("https://example.com/foo").unwrap();
3290        assert_eq!(url, "https://example.com/foo");
3291
3292        // Confirm Goose can build a base_url that includes a path.
3293        const HOST_WITH_PATH: &str = "http://example.com/with/path/";
3294        let base_url = get_base_url(Some(HOST_WITH_PATH.to_string()), None, None).unwrap();
3295        let user = GooseUser::new(0, "".to_string(), base_url, &configuration, 0, None).unwrap();
3296
3297        // Confirm the URLs are correctly built using the default_host that includes a path.
3298        let url = user.build_url("foo").unwrap();
3299        assert_eq!(&url, &[HOST_WITH_PATH, "foo"].concat());
3300        let url = user.build_url("bar/").unwrap();
3301        assert_eq!(&url, &[HOST_WITH_PATH, "bar/"].concat());
3302        let url = user.build_url("foo/bar").unwrap();
3303        assert_eq!(&url, &[HOST_WITH_PATH, "foo/bar"].concat());
3304
3305        // Confirm that URLs are correctly re-written if an absolute path is used.
3306        let url = user.build_url("/foo").unwrap();
3307        assert_eq!(&url, &[HOST, "foo"].concat());
3308    }
3309
3310    #[tokio::test]
3311    async fn manual_requests() {
3312        let server = MockServer::start();
3313
3314        let mut user = setup_user(&server).unwrap();
3315
3316        // Set up a mock http server endpoint.
3317        const INDEX_PATH: &str = "/";
3318        let index = server.mock(|when, then| {
3319            when.method(GET).path(INDEX_PATH);
3320            then.status(200);
3321        });
3322
3323        // Make a GET request to the mock http server and confirm we get a 200 response.
3324        assert_eq!(index.hits(), 0);
3325        let goose = user
3326            .get(INDEX_PATH)
3327            .await
3328            .expect("get returned unexpected error");
3329        let status = goose.response.unwrap().status();
3330        assert_eq!(status, 200);
3331        assert_eq!(goose.request.raw.method, GooseMethod::Get);
3332        assert_eq!(goose.request.name, INDEX_PATH);
3333        assert!(goose.request.success);
3334        assert!(!goose.request.update);
3335        assert_eq!(goose.request.status_code, 200);
3336        assert_eq!(index.hits(), 1);
3337
3338        const NO_SUCH_PATH: &str = "/no/such/path";
3339        // Set up a mock http server endpoint.
3340        let not_found = server.mock(|when, then| {
3341            when.method(GET).path(NO_SUCH_PATH);
3342            then.status(404);
3343        });
3344
3345        // Make an invalid GET request to the mock http server and confirm we get a 404 response.
3346        assert_eq!(not_found.hits(), 0);
3347        let goose = user
3348            .get(NO_SUCH_PATH)
3349            .await
3350            .expect("get returned unexpected error");
3351        let status = goose.response.unwrap().status();
3352        assert_eq!(status, 404);
3353        assert_eq!(goose.request.raw.method, GooseMethod::Get);
3354        assert_eq!(goose.request.name, NO_SUCH_PATH);
3355        assert!(!goose.request.success);
3356        assert!(!goose.request.update);
3357        assert_eq!(goose.request.status_code, 404,);
3358        not_found.assert_hits(1);
3359
3360        // Set up a mock http server endpoint.
3361        const COMMENT_PATH: &str = "/comment";
3362        let comment = server.mock(|when, then| {
3363            when.method(POST).path(COMMENT_PATH).body("foo");
3364            then.status(200).body("foo");
3365        });
3366
3367        // Make a POST request to the mock http server and confirm we get a 200 OK response.
3368        assert_eq!(comment.hits(), 0);
3369        let goose = user
3370            .post(COMMENT_PATH, "foo")
3371            .await
3372            .expect("post returned unexpected error");
3373        let unwrapped_response = goose.response.unwrap();
3374        let status = unwrapped_response.status();
3375        assert_eq!(status, 200);
3376        let body = unwrapped_response.text().await.unwrap();
3377        assert_eq!(body, "foo");
3378        assert_eq!(goose.request.raw.method, GooseMethod::Post);
3379        assert!(goose.request.success);
3380        assert!(!goose.request.update);
3381        assert_eq!(goose.request.status_code, 200);
3382        comment.assert_hits(1);
3383    }
3384
3385    #[test]
3386    fn test_set_session_data() {
3387        #[derive(Debug, PartialEq, Eq, Clone)]
3388        struct CustomSessionData {
3389            data: String,
3390        }
3391
3392        let session_data = CustomSessionData {
3393            data: "foo".to_owned(),
3394        };
3395
3396        let configuration = GooseConfiguration::parse_args_default(&EMPTY_ARGS).unwrap();
3397        let mut user =
3398            GooseUser::single("http://localhost:8080".parse().unwrap(), &configuration).unwrap();
3399
3400        user.set_session_data(session_data.clone());
3401
3402        let session = user.get_session_data::<CustomSessionData>();
3403        assert!(session.is_some());
3404        assert_eq!(session.unwrap(), &session_data);
3405
3406        let session = user.get_session_data_unchecked::<CustomSessionData>();
3407        assert_eq!(session, &session_data);
3408    }
3409
3410    #[test]
3411    fn test_get_mut_session_data() {
3412        #[derive(Debug, Clone)]
3413        struct CustomSessionData {
3414            data: String,
3415        }
3416
3417        let session_data = CustomSessionData {
3418            data: "foo".to_owned(),
3419        };
3420
3421        let configuration = GooseConfiguration::parse_args_default(&EMPTY_ARGS).unwrap();
3422        let mut user =
3423            GooseUser::single("http://localhost:8080".parse().unwrap(), &configuration).unwrap();
3424
3425        user.set_session_data(session_data);
3426
3427        if let Some(session) = user.get_session_data_mut::<CustomSessionData>() {
3428            "bar".clone_into(&mut session.data);
3429        }
3430
3431        let session = user.get_session_data_unchecked::<CustomSessionData>();
3432        assert_eq!(session.data, "bar".to_string());
3433
3434        let session = user.get_session_data_unchecked_mut::<CustomSessionData>();
3435        "foo".clone_into(&mut session.data);
3436        let session = user.get_session_data_unchecked::<CustomSessionData>();
3437        assert_eq!(session.data, "foo".to_string());
3438    }
3439
3440    #[test]
3441    fn test_set_session_data_override() {
3442        #[derive(Debug, Clone)]
3443        struct CustomSessionData {
3444            data: String,
3445        }
3446
3447        let mut session_data = CustomSessionData {
3448            data: "foo".to_owned(),
3449        };
3450
3451        let configuration = GooseConfiguration::parse_args_default(&EMPTY_ARGS).unwrap();
3452        let mut user =
3453            GooseUser::single("http://localhost:8080".parse().unwrap(), &configuration).unwrap();
3454
3455        user.set_session_data(session_data.clone());
3456
3457        "bar".clone_into(&mut session_data.data);
3458        user.set_session_data(session_data);
3459
3460        let session = user.get_session_data_unchecked::<CustomSessionData>();
3461        assert_eq!(session.data, "bar".to_string());
3462    }
3463}