Skip to main content

cloud_sdk_testkit/
metadata.rs

1//! Provider-neutral response metadata fixtures.
2
3use cloud_sdk::rate_limit::RateLimit;
4
5/// Fixture metadata validation error.
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub enum FixtureMetadataError {
8    /// Pagination page and page-size values must be nonzero.
9    PaginationZero,
10    /// Current page must not exceed the last page.
11    PageAfterLast,
12    /// Last page does not match total entries and page size.
13    InvalidLastPage,
14    /// Remaining requests must not exceed the rate limit.
15    RemainingExceedsLimit,
16    /// Rate limits must be nonzero.
17    RateLimitZero,
18    /// Action progress must be in `0..=100`.
19    InvalidProgress,
20}
21
22/// Pagination metadata for a deterministic response.
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub struct PaginationFixture {
25    page: u64,
26    per_page: u64,
27    total_entries: u64,
28    last_page: u64,
29}
30
31impl PaginationFixture {
32    /// Creates coherent pagination metadata.
33    pub const fn new(
34        page: u64,
35        per_page: u64,
36        total_entries: u64,
37        last_page: u64,
38    ) -> Result<Self, FixtureMetadataError> {
39        if page == 0 || per_page == 0 || last_page == 0 {
40            return Err(FixtureMetadataError::PaginationZero);
41        }
42        if page > last_page {
43            return Err(FixtureMetadataError::PageAfterLast);
44        }
45        let mut expected_last = match total_entries.checked_div(per_page) {
46            Some(value) => value,
47            None => return Err(FixtureMetadataError::PaginationZero),
48        };
49        let remainder = match total_entries.checked_rem(per_page) {
50            Some(value) => value,
51            None => return Err(FixtureMetadataError::PaginationZero),
52        };
53        if remainder != 0 {
54            expected_last = match expected_last.checked_add(1) {
55                Some(value) => value,
56                None => return Err(FixtureMetadataError::InvalidLastPage),
57            };
58        }
59        if expected_last == 0 {
60            expected_last = 1;
61        }
62        if last_page != expected_last {
63            return Err(FixtureMetadataError::InvalidLastPage);
64        }
65        Ok(Self {
66            page,
67            per_page,
68            total_entries,
69            last_page,
70        })
71    }
72
73    /// Returns the current page.
74    #[must_use]
75    pub const fn page(self) -> u64 {
76        self.page
77    }
78
79    /// Returns the requested page size.
80    #[must_use]
81    pub const fn per_page(self) -> u64 {
82        self.per_page
83    }
84
85    /// Returns the total entry count.
86    #[must_use]
87    pub const fn total_entries(self) -> u64 {
88        self.total_entries
89    }
90
91    /// Returns the last page number.
92    #[must_use]
93    pub const fn last_page(self) -> u64 {
94        self.last_page
95    }
96}
97
98/// Provider-neutral action lifecycle fixture.
99#[derive(Clone, Copy, Debug, Eq, PartialEq)]
100pub enum ActionState {
101    /// Action remains in progress.
102    Running,
103    /// Action completed successfully.
104    Success,
105    /// Action completed with an error.
106    Error,
107}
108
109/// Action metadata for polling tests.
110#[derive(Clone, Copy, Debug, Eq, PartialEq)]
111pub struct ActionFixture {
112    state: ActionState,
113    progress: u8,
114}
115
116impl ActionFixture {
117    /// Creates bounded action metadata.
118    pub const fn new(state: ActionState, progress: u8) -> Result<Self, FixtureMetadataError> {
119        if progress > 100 {
120            return Err(FixtureMetadataError::InvalidProgress);
121        }
122        Ok(Self { state, progress })
123    }
124
125    /// Returns the lifecycle state.
126    #[must_use]
127    pub const fn state(self) -> ActionState {
128        self.state
129    }
130
131    /// Returns progress in `0..=100`.
132    #[must_use]
133    pub const fn progress(self) -> u8 {
134        self.progress
135    }
136}
137
138/// Rate-limit metadata fixture.
139#[derive(Clone, Copy, Debug, Eq, PartialEq)]
140pub struct RateLimitFixture {
141    limit: u64,
142    remaining: u64,
143    reset_at: u64,
144}
145
146impl RateLimitFixture {
147    /// Creates coherent rate-limit metadata.
148    pub const fn new(
149        limit: u64,
150        remaining: u64,
151        reset_at: u64,
152    ) -> Result<Self, FixtureMetadataError> {
153        if limit == 0 {
154            return Err(FixtureMetadataError::RateLimitZero);
155        }
156        if remaining > limit {
157            return Err(FixtureMetadataError::RemainingExceedsLimit);
158        }
159        Ok(Self {
160            limit,
161            remaining,
162            reset_at,
163        })
164    }
165
166    /// Returns the request limit.
167    #[must_use]
168    pub const fn limit(self) -> u64 {
169        self.limit
170    }
171
172    /// Returns the remaining request count.
173    #[must_use]
174    pub const fn remaining(self) -> u64 {
175        self.remaining
176    }
177
178    /// Returns the provider-specific reset timestamp.
179    #[must_use]
180    pub const fn reset_at(self) -> u64 {
181        self.reset_at
182    }
183
184    pub(crate) const fn into_rate_limit(self) -> Result<RateLimit, FixtureMetadataError> {
185        match RateLimit::new(self.limit, self.remaining, self.reset_at) {
186            Ok(value) => Ok(value),
187            Err(_) => Err(FixtureMetadataError::RemainingExceedsLimit),
188        }
189    }
190}