Skip to main content

google_search_console_api/mobile_friendly_test/
mod.rs

1//! Mobile Friendly Test API types.
2//!
3//! Types for testing whether pages are mobile-friendly.
4
5use serde_derive::{Deserialize, Serialize};
6
7/// Request parameters for the mobile-friendly test.
8///
9/// # Example
10///
11/// ```rust
12/// use google_search_console_api::mobile_friendly_test::RequestMobileFriendlyTest;
13///
14/// let request = RequestMobileFriendlyTest::new("https://example.com/")
15///     .with_screenshot();
16/// ```
17#[derive(Debug, Serialize, Deserialize, Clone, Default)]
18pub struct RequestMobileFriendlyTest {
19    /// The URL to test.
20    pub url: String,
21    /// Whether to request a screenshot.
22    #[serde(rename = "requestScreenshot")]
23    pub request_screenshot: bool,
24}
25
26impl RequestMobileFriendlyTest {
27    /// Create a new request with the given URL.
28    ///
29    /// # Arguments
30    ///
31    /// * `url` - The URL to test for mobile-friendliness
32    pub fn new(url: &str) -> Self {
33        Self {
34            url: url.to_string(),
35            request_screenshot: false,
36        }
37    }
38
39    /// Request a screenshot of the rendered page.
40    ///
41    /// The screenshot will be included in the response as base64-encoded data.
42    pub fn with_screenshot(mut self) -> Self {
43        self.request_screenshot = true;
44        self
45    }
46}
47
48/// Response from the mobile-friendly test.
49#[derive(Debug, Serialize, Deserialize, Clone)]
50pub struct ResponseMobileFriendlyTest {
51    /// Status of the test.
52    #[serde(rename = "testStatus")]
53    pub test_status: TestStatus,
54    /// Whether the page is mobile-friendly.
55    #[serde(rename = "mobileFriendliness")]
56    pub mobile_friendliness: MobileFriendlyTestResult,
57    /// List of mobile-friendly issues found.
58    #[serde(rename = "mobileFriendlyIssues")]
59    pub mobile_friendly_issues: Option<Vec<MobileFriendlyIssue>>,
60    /// List of resource loading issues.
61    #[serde(rename = "resourceIssues")]
62    pub resource_issues: Option<Vec<ResourceIssue>>,
63    /// Screenshot of the page (if requested).
64    pub screenshot: Option<Image>,
65}
66
67/// Status of the mobile-friendly test.
68#[derive(Debug, Serialize, Deserialize, Clone)]
69pub struct TestStatus {
70    /// Status code.
71    pub status: TestStatusEnum,
72    /// Additional details about the status.
73    pub details: Option<String>,
74}
75
76/// Test status codes.
77#[derive(Debug, Serialize, Deserialize, Clone)]
78pub enum TestStatusEnum {
79    /// Unspecified status.
80    #[serde(rename = "TEST_STATUS_UNSPECIFIED")]
81    TestStatusUnspecified,
82    /// Test completed successfully.
83    #[serde(rename = "COMPLETE")]
84    COMPLETE,
85    /// Internal error occurred.
86    #[serde(rename = "INTERNAL_ERROR")]
87    InternalError,
88    /// Page could not be reached.
89    #[serde(rename = "PAGE_UNREACHABLE")]
90    PageUnreachable,
91}
92
93/// Mobile-friendly test result.
94#[derive(Debug, Serialize, Deserialize, Clone)]
95pub enum MobileFriendlyTestResult {
96    /// Unspecified result.
97    #[serde(rename = "MOBILE_FRIENDLY_TEST_RESULT_UNSPECIFIED")]
98    MobileFriendlyTestResultUnspecified,
99    /// Page is mobile-friendly.
100    #[serde(rename = "MOBILE_FRIENDLY")]
101    MobileFriendly,
102    /// Page is not mobile-friendly.
103    #[serde(rename = "NOT_MOBILE_FRIENDLY")]
104    NotMobileFriendly,
105}
106
107/// A mobile-friendly issue.
108#[derive(Debug, Serialize, Deserialize, Clone)]
109pub struct MobileFriendlyIssue {
110    /// The rule that was violated.
111    pub rule: MobileFriendlyRule,
112}
113
114/// Mobile-friendly rules.
115#[derive(Debug, Serialize, Deserialize, Clone)]
116pub enum MobileFriendlyRule {
117    /// Unspecified rule.
118    #[serde(rename = "MOBILE_FRIENDLY_RULE_UNSPECIFIED")]
119    MobileFriendlyRuleUnspecified,
120    /// Uses incompatible plugins (e.g., Flash).
121    #[serde(rename = "USES_INCOMPATIBLE_PLUGINS")]
122    UsesIncompatiblePlugins,
123    /// Viewport not configured.
124    #[serde(rename = "ConfigureViewport")]
125    ConfigureViewport,
126    /// Fixed-width viewport.
127    #[serde(rename = "FIXED_WIDTH_VIEWPORT")]
128    FixedWidthViewport,
129    /// Content wider than screen.
130    #[serde(rename = "SIZE_CONTENT_TO_VIEWPORT")]
131    SizeContentToViewport,
132    /// Text too small to read.
133    #[serde(rename = "USE_LEGIBLE_FONT_SIZES")]
134    UseLegibleFontSizes,
135    /// Tap targets too close together.
136    #[serde(rename = "TAP_TARGETS_TOO_CLOSE")]
137    TapTargetsTooClose,
138}
139
140/// A resource loading issue.
141#[derive(Debug, Serialize, Deserialize, Clone)]
142pub struct ResourceIssue {
143    /// Information about the blocked resource.
144    #[serde(rename = "blockedResource")]
145    pub blocked_resource: BlockedResource,
146}
147
148/// Information about a blocked resource.
149#[derive(Debug, Serialize, Deserialize, Clone)]
150pub struct BlockedResource {
151    /// URL of the blocked resource.
152    pub url: String,
153}
154
155/// Screenshot image data.
156#[derive(Debug, Serialize, Deserialize, Clone)]
157pub struct Image {
158    /// Base64-encoded image data.
159    pub data: String,
160    /// MIME type of the image (e.g., "image/png").
161    #[serde(rename = "mimeType")]
162    pub mime_type: String,
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn test_request_new() {
171        let request = RequestMobileFriendlyTest::new("https://example.com/");
172        assert_eq!(request.url, "https://example.com/");
173        assert!(!request.request_screenshot);
174    }
175
176    #[test]
177    fn test_request_with_screenshot() {
178        let request = RequestMobileFriendlyTest::new("https://example.com/").with_screenshot();
179        assert_eq!(request.url, "https://example.com/");
180        assert!(request.request_screenshot);
181    }
182
183    #[test]
184    fn test_request_default() {
185        let request = RequestMobileFriendlyTest::default();
186        assert_eq!(request.url, "");
187        assert!(!request.request_screenshot);
188    }
189
190    #[test]
191    fn test_request_serialize() {
192        let request = RequestMobileFriendlyTest::new("https://example.com/").with_screenshot();
193
194        let json = serde_json::to_string(&request).unwrap();
195        assert!(json.contains("\"url\":\"https://example.com/\""));
196        assert!(json.contains("\"requestScreenshot\":true"));
197    }
198
199    #[test]
200    fn test_response_deserialize() {
201        let json = r#"{
202            "testStatus": {
203                "status": "COMPLETE"
204            },
205            "mobileFriendliness": "MOBILE_FRIENDLY"
206        }"#;
207
208        let response: ResponseMobileFriendlyTest = serde_json::from_str(json).unwrap();
209        assert!(matches!(response.test_status.status, TestStatusEnum::COMPLETE));
210        assert!(matches!(
211            response.mobile_friendliness,
212            MobileFriendlyTestResult::MobileFriendly
213        ));
214    }
215
216    #[test]
217    fn test_response_with_issues() {
218        let json = r#"{
219            "testStatus": {
220                "status": "COMPLETE"
221            },
222            "mobileFriendliness": "NOT_MOBILE_FRIENDLY",
223            "mobileFriendlyIssues": [
224                {"rule": "TAP_TARGETS_TOO_CLOSE"}
225            ]
226        }"#;
227
228        let response: ResponseMobileFriendlyTest = serde_json::from_str(json).unwrap();
229        assert!(matches!(
230            response.mobile_friendliness,
231            MobileFriendlyTestResult::NotMobileFriendly
232        ));
233
234        let issues = response.mobile_friendly_issues.unwrap();
235        assert_eq!(issues.len(), 1);
236        assert!(matches!(
237            issues[0].rule,
238            MobileFriendlyRule::TapTargetsTooClose
239        ));
240    }
241
242    #[test]
243    fn test_test_status_enum_deserialize() {
244        assert!(matches!(
245            serde_json::from_str::<TestStatusEnum>("\"COMPLETE\"").unwrap(),
246            TestStatusEnum::COMPLETE
247        ));
248        assert!(matches!(
249            serde_json::from_str::<TestStatusEnum>("\"INTERNAL_ERROR\"").unwrap(),
250            TestStatusEnum::InternalError
251        ));
252        assert!(matches!(
253            serde_json::from_str::<TestStatusEnum>("\"PAGE_UNREACHABLE\"").unwrap(),
254            TestStatusEnum::PageUnreachable
255        ));
256    }
257}