Skip to main content

mockserver_client/
model.rs

1//! Domain model types for the MockServer control-plane API.
2//!
3//! All types implement `Serialize`/`Deserialize` and use builder methods that
4//! take `self` and return `Self`, enabling fluent construction.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9// ---------------------------------------------------------------------------
10// HttpRequest
11// ---------------------------------------------------------------------------
12
13/// Matcher for an HTTP request. Uses builder methods for fluent construction.
14///
15/// # Example
16/// ```
17/// use mockserver_client::HttpRequest;
18///
19/// let request = HttpRequest::new()
20///     .method("POST")
21///     .path("/api/users")
22///     .header("Content-Type", "application/json")
23///     .query_param("page", "1")
24///     .body("{}");
25/// ```
26#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
27#[serde(rename_all = "camelCase")]
28pub struct HttpRequest {
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub method: Option<String>,
31
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub path: Option<String>,
34
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub query_string_parameters: Option<HashMap<String, Vec<String>>>,
37
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub headers: Option<HashMap<String, Vec<String>>>,
40
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub body: Option<Body>,
43}
44
45impl HttpRequest {
46    /// Create a new empty request matcher.
47    pub fn new() -> Self {
48        Self::default()
49    }
50
51    /// Set the HTTP method to match.
52    pub fn method(mut self, method: impl Into<String>) -> Self {
53        self.method = Some(method.into());
54        self
55    }
56
57    /// Set the path to match.
58    pub fn path(mut self, path: impl Into<String>) -> Self {
59        self.path = Some(path.into());
60        self
61    }
62
63    /// Add a query string parameter (multiple values per key supported).
64    pub fn query_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
65        let params = self.query_string_parameters.get_or_insert_with(HashMap::new);
66        params
67            .entry(key.into())
68            .or_default()
69            .push(value.into());
70        self
71    }
72
73    /// Add a header (multiple values per key supported).
74    pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
75        let headers = self.headers.get_or_insert_with(HashMap::new);
76        headers
77            .entry(key.into())
78            .or_default()
79            .push(value.into());
80        self
81    }
82
83    /// Set a plain string body matcher.
84    pub fn body(mut self, body: impl Into<String>) -> Self {
85        self.body = Some(Body::Plain(body.into()));
86        self
87    }
88
89    /// Set a typed JSON body matcher.
90    pub fn json_body(mut self, json: serde_json::Value) -> Self {
91        self.body = Some(Body::Typed {
92            body_type: "JSON".to_string(),
93            json: json.to_string(),
94        });
95        self
96    }
97}
98
99// ---------------------------------------------------------------------------
100// Body
101// ---------------------------------------------------------------------------
102
103/// Request/response body — either a plain string or a typed object.
104#[derive(Debug, Clone, PartialEq)]
105pub enum Body {
106    /// A plain string body.
107    Plain(String),
108    /// A typed body (e.g., JSON).
109    Typed { body_type: String, json: String },
110}
111
112impl Serialize for Body {
113    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
114    where
115        S: serde::Serializer,
116    {
117        match self {
118            Body::Plain(s) => serializer.serialize_str(s),
119            Body::Typed { body_type, json } => {
120                use serde::ser::SerializeMap;
121                let mut map = serializer.serialize_map(Some(2))?;
122                map.serialize_entry("type", body_type)?;
123                map.serialize_entry("json", json)?;
124                map.end()
125            }
126        }
127    }
128}
129
130impl<'de> Deserialize<'de> for Body {
131    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
132    where
133        D: serde::Deserializer<'de>,
134    {
135        use serde_json::Value;
136        let v = Value::deserialize(deserializer)?;
137        match v {
138            Value::String(s) => Ok(Body::Plain(s)),
139            Value::Object(map) => {
140                let body_type = map
141                    .get("type")
142                    .and_then(|v| v.as_str())
143                    .unwrap_or("JSON")
144                    .to_string();
145                let json = map
146                    .get("json")
147                    .and_then(|v| v.as_str())
148                    .unwrap_or("")
149                    .to_string();
150                Ok(Body::Typed { body_type, json })
151            }
152            _ => Ok(Body::Plain(v.to_string())),
153        }
154    }
155}
156
157// ---------------------------------------------------------------------------
158// HttpResponse
159// ---------------------------------------------------------------------------
160
161/// Builder for an HTTP response action.
162///
163/// # Example
164/// ```
165/// use mockserver_client::HttpResponse;
166///
167/// let response = HttpResponse::new()
168///     .status_code(201)
169///     .header("Location", "/api/users/42")
170///     .body("{\"id\": 42}");
171/// ```
172#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
173#[serde(rename_all = "camelCase")]
174pub struct HttpResponse {
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub status_code: Option<u16>,
177
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub headers: Option<HashMap<String, Vec<String>>>,
180
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub body: Option<String>,
183
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub delay: Option<Delay>,
186}
187
188impl HttpResponse {
189    /// Create a new empty response.
190    pub fn new() -> Self {
191        Self::default()
192    }
193
194    /// Set the HTTP status code.
195    pub fn status_code(mut self, code: u16) -> Self {
196        self.status_code = Some(code);
197        self
198    }
199
200    /// Add a response header.
201    pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
202        let headers = self.headers.get_or_insert_with(HashMap::new);
203        headers
204            .entry(key.into())
205            .or_default()
206            .push(value.into());
207        self
208    }
209
210    /// Set the response body as a string.
211    pub fn body(mut self, body: impl Into<String>) -> Self {
212        self.body = Some(body.into());
213        self
214    }
215
216    /// Set a response delay.
217    pub fn delay(mut self, delay: Delay) -> Self {
218        self.delay = Some(delay);
219        self
220    }
221}
222
223// ---------------------------------------------------------------------------
224// HttpForward
225// ---------------------------------------------------------------------------
226
227/// Forward action — proxy the matched request to another host.
228///
229/// # Example
230/// ```
231/// use mockserver_client::HttpForward;
232///
233/// let forward = HttpForward::new("backend.local", 8080);
234/// ```
235#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
236#[serde(rename_all = "camelCase")]
237pub struct HttpForward {
238    pub host: String,
239
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub port: Option<u16>,
242
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub scheme: Option<String>,
245}
246
247impl HttpForward {
248    /// Create a forward action to the given host and port.
249    pub fn new(host: impl Into<String>, port: u16) -> Self {
250        Self {
251            host: host.into(),
252            port: Some(port),
253            scheme: None,
254        }
255    }
256
257    /// Set the scheme (HTTP or HTTPS).
258    pub fn scheme(mut self, scheme: impl Into<String>) -> Self {
259        self.scheme = Some(scheme.into());
260        self
261    }
262}
263
264// ---------------------------------------------------------------------------
265// HttpError
266// ---------------------------------------------------------------------------
267
268/// Error action — return a connection-level error to the caller.
269#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
270#[serde(rename_all = "camelCase")]
271pub struct HttpError {
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub drop_connection: Option<bool>,
274
275    #[serde(skip_serializing_if = "Option::is_none")]
276    pub response_bytes: Option<String>,
277}
278
279impl HttpError {
280    /// Create a new error action.
281    pub fn new() -> Self {
282        Self::default()
283    }
284
285    /// Drop the connection without a response.
286    pub fn drop_connection(mut self, drop: bool) -> Self {
287        self.drop_connection = Some(drop);
288        self
289    }
290
291    /// Send arbitrary bytes then close.
292    pub fn response_bytes(mut self, bytes: impl Into<String>) -> Self {
293        self.response_bytes = Some(bytes.into());
294        self
295    }
296}
297
298// ---------------------------------------------------------------------------
299// Delay
300// ---------------------------------------------------------------------------
301
302/// A time delay (e.g., for response delays).
303#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
304#[serde(rename_all = "camelCase")]
305pub struct Delay {
306    pub time_unit: String,
307    pub value: u64,
308}
309
310impl Delay {
311    /// Create a delay in milliseconds.
312    pub fn milliseconds(value: u64) -> Self {
313        Self {
314            time_unit: "MILLISECONDS".to_string(),
315            value,
316        }
317    }
318
319    /// Create a delay in seconds.
320    pub fn seconds(value: u64) -> Self {
321        Self {
322            time_unit: "SECONDS".to_string(),
323            value,
324        }
325    }
326}
327
328// ---------------------------------------------------------------------------
329// Times
330// ---------------------------------------------------------------------------
331
332/// How many times an expectation should be matched.
333#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
334#[serde(rename_all = "camelCase")]
335pub struct Times {
336    #[serde(skip_serializing_if = "Option::is_none")]
337    pub remaining_times: Option<u32>,
338
339    pub unlimited: bool,
340}
341
342impl Times {
343    /// Match unlimited times.
344    pub fn unlimited() -> Self {
345        Self {
346            remaining_times: None,
347            unlimited: true,
348        }
349    }
350
351    /// Match exactly `n` times.
352    pub fn exactly(n: u32) -> Self {
353        Self {
354            remaining_times: Some(n),
355            unlimited: false,
356        }
357    }
358
359    /// Match once.
360    pub fn once() -> Self {
361        Self::exactly(1)
362    }
363}
364
365// ---------------------------------------------------------------------------
366// TimeToLive
367// ---------------------------------------------------------------------------
368
369/// How long an expectation remains active.
370#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
371#[serde(rename_all = "camelCase")]
372pub struct TimeToLive {
373    #[serde(skip_serializing_if = "Option::is_none")]
374    pub time_unit: Option<String>,
375
376    #[serde(skip_serializing_if = "Option::is_none")]
377    pub time_to_live: Option<u64>,
378
379    pub unlimited: bool,
380}
381
382impl TimeToLive {
383    /// Unlimited TTL (never expires).
384    pub fn unlimited() -> Self {
385        Self {
386            time_unit: None,
387            time_to_live: None,
388            unlimited: true,
389        }
390    }
391
392    /// Expire after the given number of seconds.
393    pub fn seconds(seconds: u64) -> Self {
394        Self {
395            time_unit: Some("SECONDS".to_string()),
396            time_to_live: Some(seconds),
397            unlimited: false,
398        }
399    }
400
401    /// Expire after the given number of milliseconds.
402    pub fn milliseconds(millis: u64) -> Self {
403        Self {
404            time_unit: Some("MILLISECONDS".to_string()),
405            time_to_live: Some(millis),
406            unlimited: false,
407        }
408    }
409}
410
411// ---------------------------------------------------------------------------
412// VerificationTimes
413// ---------------------------------------------------------------------------
414
415/// Verification constraints — how many times a request must have been received.
416#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
417#[serde(rename_all = "camelCase")]
418pub struct VerificationTimes {
419    #[serde(skip_serializing_if = "Option::is_none")]
420    pub at_least: Option<u32>,
421
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub at_most: Option<u32>,
424}
425
426impl VerificationTimes {
427    /// Require at least `n` matching requests.
428    pub fn at_least(n: u32) -> Self {
429        Self {
430            at_least: Some(n),
431            at_most: None,
432        }
433    }
434
435    /// Require at most `n` matching requests.
436    pub fn at_most(n: u32) -> Self {
437        Self {
438            at_least: None,
439            at_most: Some(n),
440        }
441    }
442
443    /// Require exactly `n` matching requests.
444    pub fn exactly(n: u32) -> Self {
445        Self {
446            at_least: Some(n),
447            at_most: Some(n),
448        }
449    }
450
451    /// Require between `min` and `max` matching requests (inclusive).
452    pub fn between(min: u32, max: u32) -> Self {
453        Self {
454            at_least: Some(min),
455            at_most: Some(max),
456        }
457    }
458}
459
460// ---------------------------------------------------------------------------
461// Expectation
462// ---------------------------------------------------------------------------
463
464/// A full expectation combining a request matcher with an action.
465#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
466#[serde(rename_all = "camelCase")]
467pub struct Expectation {
468    #[serde(skip_serializing_if = "Option::is_none")]
469    pub id: Option<String>,
470
471    #[serde(skip_serializing_if = "Option::is_none")]
472    pub priority: Option<i32>,
473
474    pub http_request: HttpRequest,
475
476    #[serde(skip_serializing_if = "Option::is_none")]
477    pub http_response: Option<HttpResponse>,
478
479    #[serde(skip_serializing_if = "Option::is_none")]
480    pub http_forward: Option<HttpForward>,
481
482    #[serde(skip_serializing_if = "Option::is_none")]
483    pub http_error: Option<HttpError>,
484
485    #[serde(skip_serializing_if = "Option::is_none")]
486    pub times: Option<Times>,
487
488    #[serde(skip_serializing_if = "Option::is_none")]
489    pub time_to_live: Option<TimeToLive>,
490}
491
492impl Expectation {
493    /// Create a new expectation with the given request matcher.
494    pub fn new(request: HttpRequest) -> Self {
495        Self {
496            http_request: request,
497            ..Default::default()
498        }
499    }
500
501    /// Set the expectation ID (for upsert semantics).
502    pub fn id(mut self, id: impl Into<String>) -> Self {
503        self.id = Some(id.into());
504        self
505    }
506
507    /// Set the priority (higher = matched first).
508    pub fn priority(mut self, priority: i32) -> Self {
509        self.priority = Some(priority);
510        self
511    }
512
513    /// Set a response action.
514    pub fn respond(mut self, response: HttpResponse) -> Self {
515        self.http_response = Some(response);
516        self
517    }
518
519    /// Set a forward action.
520    pub fn forward(mut self, forward: HttpForward) -> Self {
521        self.http_forward = Some(forward);
522        self
523    }
524
525    /// Set an error action.
526    pub fn error(mut self, error: HttpError) -> Self {
527        self.http_error = Some(error);
528        self
529    }
530
531    /// Set the number of times this expectation matches.
532    pub fn times(mut self, times: Times) -> Self {
533        self.times = Some(times);
534        self
535    }
536
537    /// Set the time-to-live.
538    pub fn time_to_live(mut self, ttl: TimeToLive) -> Self {
539        self.time_to_live = Some(ttl);
540        self
541    }
542}
543
544// ---------------------------------------------------------------------------
545// Verification
546// ---------------------------------------------------------------------------
547
548/// A verification request sent to MockServer.
549#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
550#[serde(rename_all = "camelCase")]
551pub struct Verification {
552    pub http_request: HttpRequest,
553
554    #[serde(skip_serializing_if = "Option::is_none")]
555    pub times: Option<VerificationTimes>,
556}
557
558/// A verification sequence request.
559#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
560#[serde(rename_all = "camelCase")]
561pub struct VerificationSequence {
562    pub http_requests: Vec<HttpRequest>,
563}
564
565// ---------------------------------------------------------------------------
566// Ports
567// ---------------------------------------------------------------------------
568
569/// Port list (used by status and bind endpoints).
570#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
571pub struct Ports {
572    pub ports: Vec<u16>,
573}
574
575// ---------------------------------------------------------------------------
576// Retrieve types
577// ---------------------------------------------------------------------------
578
579/// The type of data to retrieve from MockServer.
580#[derive(Debug, Clone, Copy, PartialEq, Eq)]
581pub enum RetrieveType {
582    /// Recorded inbound requests.
583    Requests,
584    /// Active (live) expectations.
585    ActiveExpectations,
586    /// Recorded expectations (from proxy mode).
587    RecordedExpectations,
588    /// Log messages.
589    Logs,
590    /// Request/response pairs.
591    RequestResponses,
592}
593
594impl RetrieveType {
595    /// The query parameter value for this type.
596    pub fn as_str(&self) -> &'static str {
597        match self {
598            RetrieveType::Requests => "REQUESTS",
599            RetrieveType::ActiveExpectations => "ACTIVE_EXPECTATIONS",
600            RetrieveType::RecordedExpectations => "RECORDED_EXPECTATIONS",
601            RetrieveType::Logs => "LOGS",
602            RetrieveType::RequestResponses => "REQUEST_RESPONSES",
603        }
604    }
605}
606
607/// The response format for retrieve calls.
608#[derive(Debug, Clone, Copy, PartialEq, Eq)]
609pub enum RetrieveFormat {
610    Json,
611    LogEntries,
612}
613
614impl RetrieveFormat {
615    /// The query parameter value for this format.
616    pub fn as_str(&self) -> &'static str {
617        match self {
618            RetrieveFormat::Json => "JSON",
619            RetrieveFormat::LogEntries => "LOG_ENTRIES",
620        }
621    }
622}
623
624/// The type of data to clear from MockServer.
625#[derive(Debug, Clone, Copy, PartialEq, Eq)]
626pub enum ClearType {
627    All,
628    Log,
629    Expectations,
630}
631
632impl ClearType {
633    /// The query parameter value for this type.
634    pub fn as_str(&self) -> &'static str {
635        match self {
636            ClearType::All => "ALL",
637            ClearType::Log => "LOG",
638            ClearType::Expectations => "EXPECTATIONS",
639        }
640    }
641}