Skip to main content

cloud_sdk_testkit/
response.rs

1//! Deterministic response fixture builders.
2
3use cloud_sdk::transport::{ResponseHeaders, StatusCode};
4
5use crate::{ActionFixture, FixtureBody, PaginationFixture, RateLimitFixture};
6
7/// Fixture response category.
8#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub enum FixtureKind {
10    /// Successful response without additional metadata.
11    Success,
12    /// Successful paginated response.
13    Pagination,
14    /// Action polling response.
15    Action,
16    /// Rate-limit response.
17    RateLimit,
18    /// Client or server error response.
19    Error,
20}
21
22/// Response fixture construction error.
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub enum ResponseFixtureError {
25    /// Error fixtures require a `4xx` or `5xx` status.
26    NonErrorStatus,
27    /// Success fixtures require a `2xx` status.
28    NonSuccessStatus,
29}
30
31impl_static_error!(ResponseFixtureError,
32    Self::NonErrorStatus => "error fixture requires an HTTP error status",
33    Self::NonSuccessStatus => "success fixture requires a successful HTTP status",
34);
35
36/// Provider-neutral response body plus optional interpreted metadata.
37#[derive(Debug)]
38pub struct ResponseFixture<'a> {
39    kind: FixtureKind,
40    status: StatusCode,
41    body: FixtureBody<'a>,
42    pagination: Option<PaginationFixture>,
43    action: Option<ActionFixture>,
44    rate_limit: Option<RateLimitFixture>,
45    content_type: Option<&'a str>,
46    headers: Option<ResponseHeaders<'a>>,
47}
48
49impl<'a> ResponseFixture<'a> {
50    /// Creates a `200 OK` response.
51    #[must_use]
52    pub const fn success(body: FixtureBody<'a>) -> Self {
53        Self::new(FixtureKind::Success, StatusCode::OK, body)
54    }
55
56    /// Creates a success response with an exact provider-owned `2xx` status.
57    pub const fn success_at(
58        status: StatusCode,
59        body: FixtureBody<'a>,
60    ) -> Result<Self, ResponseFixtureError> {
61        if !status.is_success() {
62            return Err(ResponseFixtureError::NonSuccessStatus);
63        }
64        Ok(Self::new(FixtureKind::Success, status, body))
65    }
66
67    /// Creates a `200 OK` paginated response.
68    #[must_use]
69    pub const fn paginated(body: FixtureBody<'a>, pagination: PaginationFixture) -> Self {
70        let mut fixture = Self::new(FixtureKind::Pagination, StatusCode::OK, body);
71        fixture.pagination = Some(pagination);
72        fixture
73    }
74
75    /// Creates a `200 OK` action response.
76    #[must_use]
77    pub const fn action(body: FixtureBody<'a>, action: ActionFixture) -> Self {
78        let mut fixture = Self::new(FixtureKind::Action, StatusCode::OK, body);
79        fixture.action = Some(action);
80        fixture
81    }
82
83    /// Creates a `429 Too Many Requests` response.
84    #[must_use]
85    pub const fn rate_limited(body: FixtureBody<'a>, rate_limit: RateLimitFixture) -> Self {
86        let mut fixture = Self::new(FixtureKind::RateLimit, StatusCode::TOO_MANY_REQUESTS, body);
87        fixture.rate_limit = Some(rate_limit);
88        fixture
89    }
90
91    /// Adds rate-limit metadata to any response fixture.
92    #[must_use]
93    pub const fn with_rate_limit(mut self, rate_limit: RateLimitFixture) -> Self {
94        self.rate_limit = Some(rate_limit);
95        self
96    }
97
98    /// Adds one raw response content type for transport-boundary modeling.
99    #[must_use]
100    pub const fn with_content_type(mut self, content_type: &'a str) -> Self {
101        self.content_type = Some(content_type);
102        self
103    }
104
105    /// Adds complete prevalidated response-header metadata.
106    #[must_use]
107    pub fn with_headers(mut self, headers: ResponseHeaders<'a>) -> Self {
108        self.headers = Some(headers);
109        self
110    }
111
112    /// Creates a client or server error response.
113    pub const fn error(
114        status: StatusCode,
115        body: FixtureBody<'a>,
116    ) -> Result<Self, ResponseFixtureError> {
117        if !status.is_error() {
118            return Err(ResponseFixtureError::NonErrorStatus);
119        }
120        Ok(Self::new(FixtureKind::Error, status, body))
121    }
122
123    const fn new(kind: FixtureKind, status: StatusCode, body: FixtureBody<'a>) -> Self {
124        Self {
125            kind,
126            status,
127            body,
128            pagination: None,
129            action: None,
130            rate_limit: None,
131            content_type: None,
132            headers: None,
133        }
134    }
135
136    /// Returns the fixture category.
137    #[must_use]
138    pub const fn kind(&self) -> FixtureKind {
139        self.kind
140    }
141
142    /// Returns the response status.
143    #[must_use]
144    pub const fn status(&self) -> StatusCode {
145        self.status
146    }
147
148    /// Returns the response body source.
149    #[must_use]
150    pub const fn body(&self) -> FixtureBody<'a> {
151        self.body
152    }
153
154    /// Returns pagination metadata when present.
155    #[must_use]
156    pub const fn pagination(&self) -> Option<PaginationFixture> {
157        self.pagination
158    }
159
160    /// Returns action metadata when present.
161    #[must_use]
162    pub const fn action_metadata(&self) -> Option<ActionFixture> {
163        self.action
164    }
165
166    /// Returns rate-limit metadata when present.
167    #[must_use]
168    pub const fn rate_limit(&self) -> Option<RateLimitFixture> {
169        self.rate_limit
170    }
171
172    /// Returns the response content type when configured.
173    #[must_use]
174    pub const fn content_type(&self) -> Option<&'a str> {
175        self.content_type
176    }
177
178    /// Returns complete prevalidated response-header metadata.
179    #[must_use]
180    pub const fn headers(&self) -> Option<&ResponseHeaders<'a>> {
181        self.headers.as_ref()
182    }
183}