bing_webmaster_api/
dto.rs

1//! Data Transfer Objects (DTOs) for Bing Webmaster API
2//!
3//! This module contains all data structures used for communication with the Bing Webmaster API.
4//! All structures mirror the .NET API definitions from `Microsoft.Bing.Webmaster.Api.Interfaces`.
5//!
6//! # Field Naming
7//!
8//! All fields use `#[serde(rename = "...")]` to match the PascalCase naming convention
9//! used by the .NET API, while providing idiomatic snake_case Rust field names.
10
11use chrono::NaiveDate;
12use serde::{Deserialize, Serialize};
13use serde_repr::{Deserialize_repr, Serialize_repr};
14
15/// .NET DateTime serialization format used by Bing API
16///
17/// The Bing API uses .NET's JSON date format: `/Date(timestamp-offset)/`
18/// where timestamp is milliseconds since Unix epoch.
19///
20/// # Format
21/// `/Date(1316156400000-0700)/`
22/// - `1316156400000` - milliseconds since Unix epoch
23/// - `-0700` - timezone offset (optional)
24mod dotnet_date_format {
25    use chrono::{DateTime, NaiveDate, TimeZone, Utc};
26    use serde::{Deserialize, Deserializer, Serialize, Serializer};
27
28    pub fn serialize<S>(date: &NaiveDate, serializer: S) -> Result<S::Ok, S::Error>
29    where
30        S: Serializer,
31    {
32        let timestamp_ms = date
33            .and_hms_opt(0, 0, 0)
34            .unwrap()
35            .and_utc()
36            .timestamp_millis();
37        let formatted = format!("/Date({})/", timestamp_ms);
38        formatted.serialize(serializer)
39    }
40
41    pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveDate, D::Error>
42    where
43        D: Deserializer<'de>,
44    {
45        let s = String::deserialize(deserializer)?;
46
47        // Handle special case: dates starting with /Date(- are considered null
48        if s.starts_with("/Date(-") {
49            return Err(serde::de::Error::custom("Null date value"));
50        }
51
52        // Handle .NET date format: "/Date(1316156400000-0700)/"
53        if s.starts_with("/Date(") && s.ends_with(")/") {
54            let inner = &s[6..s.len() - 2]; // Remove "/Date(" and ")/"
55
56            // Find last - or + for timezone offset
57            let (hyph_pos, is_negative) = if let Some(pos) = inner.rfind('-') {
58                (Some(pos), true)
59            } else if let Some(pos) = inner.rfind('+') {
60                (Some(pos), false)
61            } else {
62                (None, true)
63            };
64
65            if let Some(hyph) = hyph_pos {
66                // Parse timestamp before timezone offset
67                let timestamp_str = &inner[..hyph];
68                let mut timestamp_ms = timestamp_str
69                    .parse::<f64>()
70                    .map_err(|_| serde::de::Error::custom("Failed to parse timestamp"))?;
71
72                // Parse timezone offset hours (2 digits)
73                if hyph + 3 <= inner.len() {
74                    let hours_str = &inner[hyph + 1..hyph + 3];
75                    let hours = hours_str
76                        .parse::<f64>()
77                        .map_err(|_| serde::de::Error::custom("Failed to parse hours"))?;
78
79                    // Parse timezone offset minutes (2 digits)
80                    let mins = if hyph + 5 <= inner.len() {
81                        let mins_str = &inner[hyph + 3..hyph + 5];
82                        mins_str.parse::<f64>().unwrap_or(0.0)
83                    } else {
84                        0.0
85                    };
86
87                    // Apply timezone offset to convert to UTC
88                    let offset_ms = (hours * 60.0 * 60.0 * 1000.0) + (mins * 60.0 * 1000.0);
89                    if is_negative {
90                        timestamp_ms -= offset_ms;
91                    } else {
92                        timestamp_ms += offset_ms;
93                    }
94                }
95
96                // Create DateTime from adjusted timestamp
97                match Utc.timestamp_millis_opt(timestamp_ms as i64) {
98                    chrono::LocalResult::Single(dt) => Ok(dt.date_naive()),
99                    _ => Err(serde::de::Error::custom("Invalid timestamp")),
100                }
101            } else {
102                // No timezone offset - parse as timestamp and use date only (no time component)
103                let timestamp_ms = inner
104                    .parse::<i64>()
105                    .map_err(|_| serde::de::Error::custom("Failed to parse timestamp"))?;
106
107                match Utc.timestamp_millis_opt(timestamp_ms) {
108                    chrono::LocalResult::Single(dt) => {
109                        // Return date only (time set to 00:00:00)
110                        let date = dt.date_naive();
111                        Ok(date)
112                    }
113                    _ => Err(serde::de::Error::custom("Invalid timestamp")),
114                }
115            }
116        } else {
117            // Fallback to standard ISO format
118            s.parse::<DateTime<Utc>>()
119                .map(|s| s.date_naive())
120                .map_err(serde::de::Error::custom)
121        }
122    }
123}
124
125mod dotnet_date_format_opt {
126    use chrono::{DateTime, NaiveDate, TimeZone, Utc};
127    use serde::{Deserialize, Deserializer, Serialize, Serializer};
128
129    pub fn serialize<S>(date: &Option<NaiveDate>, serializer: S) -> Result<S::Ok, S::Error>
130    where
131        S: Serializer,
132    {
133        match date {
134            None => "/Date(-0)/".serialize(serializer),
135            Some(date) => {
136                let timestamp_ms = date
137                    .and_hms_opt(0, 0, 0)
138                    .unwrap()
139                    .and_utc()
140                    .timestamp_millis();
141                let formatted = format!("/Date({})/", timestamp_ms);
142                formatted.serialize(serializer)
143            }
144        }
145    }
146
147    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<NaiveDate>, D::Error>
148    where
149        D: Deserializer<'de>,
150    {
151        let s = String::deserialize(deserializer)?;
152
153        // Handle special case: dates starting with /Date(- are considered null
154        if s.starts_with("/Date(-") {
155            return Ok(None);
156        }
157
158        // Handle .NET date format: "/Date(1316156400000-0700)/"
159        if s.starts_with("/Date(") && s.ends_with(")/") {
160            let inner = &s[6..s.len() - 2]; // Remove "/Date(" and ")/"
161
162            // Find last - or + for timezone offset
163            let (hyph_pos, is_negative) = if let Some(pos) = inner.rfind('-') {
164                (Some(pos), true)
165            } else if let Some(pos) = inner.rfind('+') {
166                (Some(pos), false)
167            } else {
168                (None, true)
169            };
170
171            if let Some(hyph) = hyph_pos {
172                // Parse timestamp before timezone offset
173                let timestamp_str = &inner[..hyph];
174                let mut timestamp_ms = timestamp_str
175                    .parse::<f64>()
176                    .map_err(|_| serde::de::Error::custom("Failed to parse timestamp"))?;
177
178                // Parse timezone offset hours (2 digits)
179                if hyph + 3 <= inner.len() {
180                    let hours_str = &inner[hyph + 1..hyph + 3];
181                    let hours = hours_str
182                        .parse::<f64>()
183                        .map_err(|_| serde::de::Error::custom("Failed to parse hours"))?;
184
185                    // Parse timezone offset minutes (2 digits)
186                    let mins = if hyph + 5 <= inner.len() {
187                        let mins_str = &inner[hyph + 3..hyph + 5];
188                        mins_str.parse::<f64>().unwrap_or(0.0)
189                    } else {
190                        0.0
191                    };
192
193                    // Apply timezone offset to convert to UTC
194                    let offset_ms = (hours * 60.0 * 60.0 * 1000.0) + (mins * 60.0 * 1000.0);
195                    if is_negative {
196                        timestamp_ms -= offset_ms;
197                    } else {
198                        timestamp_ms += offset_ms;
199                    }
200                }
201
202                // Create DateTime from adjusted timestamp
203                match Utc.timestamp_millis_opt(timestamp_ms as i64) {
204                    chrono::LocalResult::Single(dt) => Ok(Some(dt.date_naive())),
205                    _ => Err(serde::de::Error::custom("Invalid timestamp")),
206                }
207            } else {
208                // No timezone offset - parse as timestamp and use date only (no time component)
209                let timestamp_ms = inner
210                    .parse::<i64>()
211                    .map_err(|_| serde::de::Error::custom("Failed to parse timestamp"))?;
212
213                match Utc.timestamp_millis_opt(timestamp_ms) {
214                    chrono::LocalResult::Single(dt) => {
215                        // Return date only (time set to 00:00:00)
216                        let date = dt.date_naive();
217                        Ok(Some(date))
218                    }
219                    _ => Err(serde::de::Error::custom("Invalid timestamp")),
220                }
221            }
222        } else {
223            // Fallback to standard ISO format
224            s.parse::<DateTime<Utc>>()
225                .map(|s| Some(s.date_naive()))
226                .map_err(serde::de::Error::custom)
227        }
228    }
229}
230
231/// Response wrapper for Bing Webmaster API JSON responses
232///
233/// All JSON responses from the Bing API are wrapped in a `{"d": data}` structure,
234/// following the .NET WCF JSON serialization format.
235#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct ResponseWrapper<T> {
237    /// The wrapped response data
238    pub d: T,
239}
240
241/// Represents a URL that has been blocked from Bing's search index
242///
243/// Used to request temporary removal of content from Bing search results.
244/// This can be for a single page or an entire directory.
245#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct BlockedUrl {
247    /// The URL to be blocked (e.g., `https://example.com/page`)
248    #[serde(rename = "Url")]
249    pub url: String,
250
251    /// The date when the block was requested
252    #[serde(rename = "Date", with = "dotnet_date_format")]
253    pub date: NaiveDate,
254
255    /// Number of days until the block expires (if applicable)
256    #[serde(rename = "DaysToExpire", skip_serializing_if = "Option::is_none")]
257    pub days_to_expire: Option<i32>,
258
259    /// Whether this blocks a single page or entire directory
260    #[serde(rename = "EntityType")]
261    pub entity_type: BlockedUrlEntityType,
262
263    /// Type of removal requested (cache only or full removal)
264    #[serde(rename = "RequestType")]
265    pub request_type: BlockedUrlRequestType,
266}
267
268/// Specifies whether a block applies to a single page or directory
269#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
270pub enum BlockedUrlEntityType {
271    /// Block a single page
272    Page = 0,
273    /// Block an entire directory and all its contents
274    Directory = 1,
275}
276
277/// Type of content removal requested
278#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
279pub enum BlockedUrlRequestType {
280    /// Remove from cache only, keep in search results
281    CacheOnly = 0,
282    /// Remove completely from search results and cache
283    FullRemoval = 1,
284}
285
286/// Reasons for blocking page previews
287#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
288pub enum BlockReason {
289    /// Don't show preview
290    NoPreview,
291    /// Don't cache the page
292    NoCache,
293    /// Don't show snippet in search results
294    NoSnippet,
295    /// Don't index the page
296    NoIndex,
297    /// Don't show archived version
298    NoArchive,
299}
300
301/// Geographic targeting settings for content
302///
303/// Allows you to specify which country or region specific content is targeted towards.
304/// This helps Bing show the right content to users in different geographic locations.
305#[derive(Debug, Clone, Serialize, Deserialize)]
306pub struct CountryRegionSettings {
307    /// The date when this setting was configured
308    #[serde(rename = "Date", with = "dotnet_date_format")]
309    pub date: NaiveDate,
310
311    /// Two-letter ISO country code (e.g., "US", "GB", "DE")
312    #[serde(rename = "TwoLetterIsoCountryCode")]
313    pub two_letter_iso_country_code: String,
314
315    /// The scope of this geographic targeting setting
316    #[serde(rename = "Type")]
317    pub r#type: CountryRegionSettingsType,
318
319    /// The URL or URL pattern this setting applies to
320    #[serde(rename = "Url")]
321    pub url: String,
322}
323
324/// Scope of geographic targeting
325#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)]
326#[repr(i32)]
327pub enum CountryRegionSettingsType {
328    /// Target a single page
329    Page = 0,
330    /// Target a directory and all its contents
331    Directory = 1,
332    /// Target the entire domain
333    Domain = 2,
334    /// Target a subdomain
335    Subdomain = 3,
336}
337
338/// Crawl statistics for a website
339///
340/// Provides detailed metrics about how Bingbot crawls your website,
341/// including HTTP response codes, errors, and index status.
342#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct CrawlStats {
344    /// Count of pages returning other HTTP status codes not categorized below
345    #[serde(rename = "AllOtherCodes")]
346    pub all_other_codes: i64,
347
348    /// Count of pages blocked by robots.txt
349    #[serde(rename = "BlockedByRobotsTxt")]
350    pub blocked_by_robots_txt: i64,
351
352    /// Count of pages returning 2xx success codes
353    #[serde(rename = "Code2xx")]
354    pub code_2xx: i64,
355
356    /// Count of pages returning 301 permanent redirect
357    #[serde(rename = "Code301")]
358    pub code_301: i64,
359
360    /// Count of pages returning 302 temporary redirect
361    #[serde(rename = "Code302")]
362    pub code_302: i64,
363
364    /// Count of pages returning 4xx client error codes
365    #[serde(rename = "Code4xx")]
366    pub code_4xx: i64,
367
368    /// Count of pages returning 5xx server error codes
369    #[serde(rename = "Code5xx")]
370    pub code_5xx: i64,
371
372    /// Count of connection timeouts
373    #[serde(rename = "ConnectionTimeout")]
374    pub connection_timeout: i64,
375
376    /// Total number of pages crawled
377    #[serde(rename = "CrawledPages")]
378    pub crawled_pages: i64,
379
380    /// Total number of crawl errors encountered
381    #[serde(rename = "CrawlErrors")]
382    pub crawl_errors: i64,
383
384    /// Date of these statistics
385    #[serde(rename = "Date", with = "dotnet_date_format")]
386    pub date: NaiveDate,
387
388    /// Count of DNS resolution failures
389    #[serde(rename = "DnsFailures")]
390    pub dns_failures: i64,
391
392    /// Number of pages currently in Bing's index
393    #[serde(rename = "InIndex")]
394    pub in_index: i64,
395
396    /// Number of inbound links to the site
397    #[serde(rename = "InLinks")]
398    pub in_links: i64,
399}
400
401/// Deep link information for search results
402///
403/// Deep links are additional links shown below a main search result,
404/// helping users navigate directly to specific pages within your site.
405#[derive(Debug, Clone, Serialize, Deserialize)]
406pub struct DeepLink {
407    /// Position of this deep link in the search results
408    #[serde(rename = "Position")]
409    pub position: i32,
410
411    /// Display title for the deep link
412    #[serde(rename = "Title")]
413    pub title: String,
414
415    /// URL of the deep link
416    #[serde(rename = "Url")]
417    pub url: String,
418
419    /// Weight/priority of this deep link
420    #[serde(rename = "Weight")]
421    pub weight: DeepLinkWeight,
422}
423
424/// Priority weight for deep links
425///
426/// Controls how prominently a deep link should be displayed in search results.
427#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
428pub enum DeepLinkWeight {
429    /// Deep link is disabled
430    Disabled = 0,
431    /// Low priority
432    Low = 1,
433    /// Normal priority
434    Normal = 2,
435    /// High priority
436    High = 3,
437}
438
439/// Algorithm-suggested deep link URL
440///
441/// URLs that Bing's algorithm suggests as good candidates for deep links.
442#[derive(Debug, Clone, Serialize, Deserialize)]
443pub struct DeepLinkAlgoUrl {
444    /// Number of deep links for this URL
445    #[serde(rename = "DeepLinkCount")]
446    pub deep_link_count: i32,
447
448    /// Number of impressions this URL receives
449    #[serde(rename = "Impressions")]
450    pub impressions: i32,
451
452    /// The suggested URL
453    #[serde(rename = "Url")]
454    pub url: String,
455}
456
457/// Blocked deep link
458///
459/// Represents a deep link that has been explicitly blocked from appearing in search results.
460#[derive(Debug, Clone, Serialize, Deserialize)]
461pub struct DeepLinkBlock {
462    /// Source URL (the main search result)
463    pub source_url: String,
464
465    /// Target URL (the deep link being blocked)
466    pub target_url: String,
467
468    /// Type of block applied
469    pub block_type: String,
470
471    /// Reason for blocking this deep link
472    pub reason: String,
473}
474
475/// Page preview block
476///
477/// Represents a page where preview features (snippet, cache, etc.) have been blocked.
478#[derive(Debug, Clone, Serialize, Deserialize)]
479pub struct PagePreviewBlock {
480    /// URL of the page with blocked preview
481    pub url: String,
482
483    /// Reason for blocking the preview
484    pub block_reason: BlockReason,
485
486    /// Date when the block was applied
487    pub blocked_date: NaiveDate,
488}
489
490/// Content submission API quota
491///
492/// Daily and monthly limits for the content submission API.
493/// Content submission allows submitting page content directly to Bing (up to 10MB per request).
494#[derive(Debug, Clone, Serialize, Deserialize)]
495pub struct ContentSubmissionQuota {
496    /// Daily submission quota remaining
497    #[serde(rename = "DailyQuota")]
498    pub daily_quota: i64,
499
500    /// Monthly submission quota remaining
501    #[serde(rename = "MonthlyQuota")]
502    pub monthly_quota: i64,
503}
504
505/// Crawl rate settings for a site
506///
507/// Controls how frequently Bingbot crawls your site.
508#[derive(Debug, Clone, Serialize, Deserialize)]
509pub struct CrawlSettings {
510    /// Whether crawl boost feature is available for this site
511    #[serde(rename = "CrawlBoostAvailable")]
512    pub crawl_boost_available: bool,
513
514    /// Whether crawl boost is currently enabled
515    #[serde(rename = "CrawlBoostEnabled")]
516    pub crawl_boost_enabled: bool,
517
518    /// Crawl rate configuration data
519    #[serde(rename = "CrawlRate")]
520    pub crawl_rate: Vec<u8>,
521}
522
523/// Detailed query statistics for a specific date
524///
525/// More granular version of `QueryStats` with position data.
526#[derive(Debug, Clone, Serialize, Deserialize)]
527pub struct DetailedQueryStats {
528    /// Number of clicks for this query
529    #[serde(rename = "Clicks")]
530    pub clicks: i64,
531
532    /// Date of these statistics
533    #[serde(rename = "Date", with = "dotnet_date_format")]
534    pub date: NaiveDate,
535
536    /// Number of impressions for this query
537    #[serde(rename = "Impressions")]
538    pub impressions: i64,
539
540    /// Average position in search results
541    #[serde(rename = "Position")]
542    pub position: f64,
543}
544
545/// Search query performance statistics
546///
547/// Contains metrics about how a specific search query performs for your site,
548/// including clicks, impressions, and ranking positions.
549#[derive(Debug, Clone, Serialize, Deserialize)]
550pub struct QueryStats {
551    /// Average position in search results when the page is clicked
552    #[serde(rename = "AvgClickPosition")]
553    pub avg_click_position: f64,
554
555    /// Average position in search results when the page is shown (impression)
556    #[serde(rename = "AvgImpressionPosition")]
557    pub avg_impression_position: f64,
558
559    /// Number of times users clicked through to your site from search results
560    #[serde(rename = "Clicks")]
561    pub clicks: i64,
562
563    /// Date of these statistics
564    #[serde(rename = "Date", with = "dotnet_date_format")]
565    pub date: NaiveDate,
566
567    /// Number of times your site appeared in search results (impressions)
568    #[serde(rename = "Impressions")]
569    pub impressions: i64,
570
571    /// The search query string
572    #[serde(rename = "Query")]
573    pub query: String,
574}
575
576/// Crawl issues encountered for a URL
577///
578/// This is a flags enumeration that supports bitwise combination of values.
579///
580/// Reference: Microsoft.Bing.Webmaster.Api.Interfaces.UrlWithCrawlIssues.CrawlIssues
581#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
582#[repr(i32)]
583pub enum CrawlIssues {
584    /// No issues
585    None = 0,
586    /// HTTP 301 permanent redirect
587    Code301 = 1,
588    /// HTTP 302 temporary redirect
589    Code302 = 2,
590    /// HTTP 4xx client error
591    Code4xx = 4,
592    /// HTTP 5xx server error
593    Code5xx = 8,
594    /// URL blocked by robots.txt
595    BlockedByRobotsTxt = 16,
596    /// Page contains malware
597    ContainsMalware = 32,
598    /// Important URL blocked by robots.txt
599    ImportantUrlBlockedByRobotsTxt = 64,
600    /// DNS resolution errors
601    DnsErrors = 128,
602    /// Request timeout errors
603    TimeOutErrors = 256,
604}
605
606/// URL with crawl issues
607///
608/// Represents a URL that Bingbot encountered problems crawling,
609/// along with information about the type of issues.
610///
611/// Reference: Microsoft.Bing.Webmaster.Api.Interfaces.UrlWithCrawlIssues
612#[derive(Debug, Clone, Serialize, Deserialize)]
613pub struct UrlWithCrawlIssues {
614    /// HTTP status code returned when crawling this URL
615    #[serde(rename = "HttpCode")]
616    pub http_code: i32,
617
618    /// Crawl issues encountered (flags enum, can be combined)
619    #[serde(rename = "Issues")]
620    pub issues: CrawlIssues,
621
622    /// The URL that has crawl issues
623    #[serde(rename = "Url")]
624    pub url: String,
625
626    /// Number of inbound links pointing to this URL
627    #[serde(rename = "InLinks")]
628    pub in_links: i64,
629}
630
631/// RSS or Atom feed information
632///
633/// Contains details about a submitted feed, including its status and crawl information.
634#[derive(Debug, Clone, Serialize, Deserialize)]
635pub struct Feed {
636    /// Whether the feed is compressed (gzipped)
637    #[serde(rename = "Compressed")]
638    pub compressed: bool,
639
640    /// Size of the feed file in bytes
641    #[serde(rename = "FileSize")]
642    pub file_size: i64,
643
644    /// When the feed was last crawled by Bing
645    #[serde(rename = "LastCrawled", with = "dotnet_date_format_opt")]
646    pub last_crawled: Option<NaiveDate>,
647
648    /// Current status of the feed (e.g., "Active", "Pending")
649    #[serde(rename = "Status")]
650    pub status: String,
651
652    /// When the feed was submitted
653    #[serde(rename = "Submitted", with = "dotnet_date_format_opt")]
654    pub submitted: Option<NaiveDate>,
655
656    /// Feed type (e.g., "RSS", "Atom")
657    #[serde(rename = "Type")]
658    pub r#type: String,
659
660    /// URL of the feed
661    #[serde(rename = "Url")]
662    pub url: String,
663
664    /// Number of URLs contained in the feed
665    #[serde(rename = "UrlCount")]
666    pub url_count: i32,
667}
668
669/// Website information and verification status
670///
671/// Contains verification codes and status for a site in Bing Webmaster Tools.
672#[derive(Debug, Clone, Serialize, Deserialize)]
673pub struct Site {
674    /// Authentication code for meta tag verification
675    #[serde(rename = "AuthenticationCode")]
676    pub authentication_code: String,
677
678    /// DNS TXT record code for DNS verification
679    #[serde(rename = "DnsVerificationCode")]
680    pub dns_verification_code: String,
681
682    /// Whether the site ownership has been verified
683    #[serde(rename = "IsVerified")]
684    pub is_verified: bool,
685
686    /// The site URL (e.g., `https://example.com`)
687    #[serde(rename = "Url")]
688    pub url: String,
689}
690
691/// User roles and permissions for a site
692///
693/// Represents a user's access permissions for a specific site in Bing Webmaster Tools.
694#[derive(Debug, Clone, Serialize, Deserialize)]
695pub struct SiteRoles {
696    /// Date when this role was assigned
697    #[serde(rename = "Date", with = "dotnet_date_format")]
698    pub date: NaiveDate,
699
700    /// Delegation code for transferring site ownership
701    #[serde(rename = "DelegatedCode")]
702    pub delegated_code: Option<String>,
703
704    /// Email of the user who owns the delegation code
705    #[serde(rename = "DelegatedCodeOwnerEmail")]
706    pub delegated_code_owner_email: Option<String>,
707
708    /// Email of the user who delegated access
709    #[serde(rename = "DelegatorEmail")]
710    pub delegator_email: Option<String>,
711
712    /// Email of the user with this role
713    #[serde(rename = "Email")]
714    pub email: String,
715
716    /// Whether this role assignment has expired
717    #[serde(rename = "Expired")]
718    pub expired: bool,
719
720    /// The role assigned to the user
721    #[serde(rename = "Role")]
722    pub role: UserRole,
723
724    /// The site URL this role applies to
725    #[serde(rename = "Site")]
726    pub site: String,
727
728    /// The verification site URL
729    #[serde(rename = "VerificationSite")]
730    pub verification_site: String,
731}
732
733/// User role permissions for site access
734///
735/// Defines the level of access a user has to a site in Bing Webmaster Tools.
736///
737/// Reference: Microsoft.Bing.Webmaster.Api.Interfaces.SiteRoles.UserRole
738#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
739#[repr(i32)]
740pub enum UserRole {
741    /// User has full administrative permissions
742    Administrator = 0,
743    /// User has read-only permissions
744    ReadOnly = 1,
745    /// User has read and write permissions
746    ReadWrite = 2,
747}
748
749/// Detailed information about a specific URL
750///
751/// Contains comprehensive metadata about a URL including crawl status,
752/// size, and link information.
753#[derive(Debug, Clone, Serialize, Deserialize)]
754pub struct UrlInfo {
755    /// Number of anchor links pointing to this URL
756    #[serde(rename = "AnchorCount")]
757    pub anchor_count: i32,
758
759    /// When Bing first discovered this URL
760    #[serde(rename = "DiscoveryDate", with = "dotnet_date_format")]
761    pub discovery_date: NaiveDate,
762
763    /// Size of the document in bytes
764    #[serde(rename = "DocumentSize")]
765    pub document_size: i64,
766
767    /// HTTP status code returned when crawling
768    #[serde(rename = "HttpStatus")]
769    pub http_status: i32,
770
771    /// Whether this is a page (true) or directory (false)
772    #[serde(rename = "IsPage")]
773    pub is_page: bool,
774
775    /// When Bing last crawled this URL
776    #[serde(rename = "LastCrawledDate", with = "dotnet_date_format")]
777    pub last_crawled_date: NaiveDate,
778
779    /// Total number of child URLs under this URL
780    #[serde(rename = "TotalChildUrlCount")]
781    pub total_child_url_count: i32,
782
783    /// The URL
784    #[serde(rename = "Url")]
785    pub url: String,
786}
787
788/// Traffic statistics for a specific URL
789///
790/// Contains click and impression data for a URL in search results.
791#[derive(Debug, Clone, Serialize, Deserialize)]
792pub struct UrlTrafficInfo {
793    /// Number of clicks this URL received
794    #[serde(rename = "Clicks")]
795    pub clicks: i32,
796
797    /// Number of times this URL appeared in search results
798    #[serde(rename = "Impressions")]
799    pub impressions: i32,
800
801    /// Whether this is a page (true) or directory (false)
802    #[serde(rename = "IsPage")]
803    pub is_page: bool,
804
805    /// The URL
806    #[serde(rename = "Url")]
807    pub url: String,
808}
809
810/// URL submission API quota
811///
812/// Daily and monthly limits for the URL submission API.
813/// Allows submitting up to 10,000 URLs per day for crawling.
814#[derive(Debug, Clone, Serialize, Deserialize)]
815pub struct UrlSubmissionQuota {
816    /// Daily URL submission quota remaining
817    #[serde(rename = "DailyQuota")]
818    pub daily_quota: i32,
819
820    /// Monthly URL submission quota remaining
821    #[serde(rename = "MonthlyQuota")]
822    pub monthly_quota: i32,
823}
824
825/// Inbound link counts for URLs
826///
827/// Contains a list of URLs and how many inbound links each has.
828#[derive(Debug, Clone, Serialize, Deserialize)]
829pub struct LinkCounts {
830    /// List of URLs with their link counts
831    #[serde(rename = "Links")]
832    pub links: Vec<LinkCount>,
833
834    /// Total number of pages in the result set
835    #[serde(rename = "TotalPages")]
836    pub total_pages: i32,
837}
838
839/// Link count for a specific URL
840#[derive(Debug, Clone, Serialize, Deserialize)]
841pub struct LinkCount {
842    /// Number of inbound links to this URL
843    #[serde(rename = "Count")]
844    pub count: i32,
845
846    /// The URL
847    #[serde(rename = "Url")]
848    pub url: String,
849}
850
851/// Detailed inbound link information
852///
853/// Contains specific details about inbound links including anchor text.
854#[derive(Debug, Clone, Serialize, Deserialize)]
855pub struct LinkDetails {
856    /// List of detailed link information
857    #[serde(rename = "Details")]
858    pub details: Vec<LinkDetail>,
859
860    /// Total number of pages in the result set
861    #[serde(rename = "TotalPages")]
862    pub total_pages: i32,
863}
864
865/// Detail about a specific inbound link
866#[derive(Debug, Clone, Serialize, Deserialize)]
867pub struct LinkDetail {
868    /// The anchor text used for the link
869    #[serde(rename = "AnchorText")]
870    pub anchor_text: String,
871
872    /// The source URL of the link
873    #[serde(rename = "Url")]
874    pub url: String,
875}
876
877/// Query parameter configuration
878///
879/// Represents a URL query parameter that should be ignored or included during crawling.
880/// This helps prevent duplicate content issues from URL parameters.
881#[derive(Debug, Clone, Serialize, Deserialize)]
882pub struct QueryParameter {
883    /// Date when this parameter configuration was set
884    #[serde(rename = "Date", with = "dotnet_date_format")]
885    pub date: NaiveDate,
886
887    /// Whether this parameter is enabled (should be ignored)
888    #[serde(rename = "IsEnabled")]
889    pub is_enabled: bool,
890
891    /// The query parameter name (e.g., "sessionid", "ref")
892    #[serde(rename = "Parameter")]
893    pub parameter: String,
894
895    /// Source of this parameter configuration
896    #[serde(rename = "Source")]
897    pub source: i32,
898}
899
900/// Combined ranking and traffic statistics
901///
902/// Aggregated metrics for site performance in search results.
903#[derive(Debug, Clone, Serialize, Deserialize)]
904pub struct RankAndTrafficStats {
905    /// Total number of clicks
906    #[serde(rename = "Clicks")]
907    pub clicks: i64,
908
909    /// Date of these statistics
910    #[serde(rename = "Date", with = "dotnet_date_format")]
911    pub date: NaiveDate,
912
913    /// Total number of impressions
914    #[serde(rename = "Impressions")]
915    pub impressions: i64,
916}
917
918/// Crawl date filter options
919///
920/// Reference: Microsoft.Bing.Webmaster.Api.Interfaces.CrawlDateFilter
921#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
922#[repr(i32)]
923pub enum CrawlDateFilter {
924    /// Any crawl date
925    Any = 0,
926    /// Last week
927    LastWeek = 1,
928    /// Last two weeks
929    LastTwoWeeks = 2,
930}
931
932/// Discovered date filter options
933///
934/// Reference: Microsoft.Bing.Webmaster.Api.Interfaces.DiscoveredDateFilter
935#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
936#[repr(i32)]
937pub enum DiscoveredDateFilter {
938    /// Any discovered date
939    Any = 0,
940    /// Last week
941    LastWeek = 1,
942    /// Last month
943    LastMonth = 2,
944}
945
946/// Document flags filters
947///
948/// Flags enumeration for filtering by document properties.
949///
950/// Reference: Microsoft.Bing.Webmaster.Api.Interfaces.DocFlagsFilters
951#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
952#[repr(i32)]
953pub enum DocFlagsFilters {
954    /// Any document flags
955    Any = 0,
956    /// Document is blocked by robots.txt
957    IsBlockedByRobotsTxt = 1,
958}
959
960/// HTTP code filters
961///
962/// Flags enumeration for filtering by HTTP status codes.
963///
964/// Reference: Microsoft.Bing.Webmaster.Api.Interfaces.HttpCodeFilters
965#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
966#[repr(i32)]
967pub enum HttpCodeFilters {
968    /// Any HTTP code
969    Any = 0,
970    /// 2xx success codes
971    Code2xx = 1,
972    /// 3xx redirect codes
973    Code3xx = 2,
974    /// 301 permanent redirect
975    Code301 = 4,
976    /// 302 temporary redirect
977    Code302 = 8,
978    /// 4xx client error codes
979    Code4xx = 16,
980    /// 5xx server error codes
981    Code5xx = 32,
982    /// All other codes
983    AllOthers = 64,
984}
985
986/// Filter properties for queries
987///
988/// Used to filter URL information queries by various criteria.
989///
990/// Reference: Microsoft.Bing.Webmaster.Api.Interfaces.FilterProperties
991#[derive(Debug, Clone, Serialize, Deserialize)]
992pub struct FilterProperties {
993    /// Filter by crawl date range
994    #[serde(rename = "CrawlDateFilter")]
995    pub crawl_date_filter: CrawlDateFilter,
996
997    /// Filter by discovered date range
998    #[serde(rename = "DiscoveredDateFilter")]
999    pub discovered_date_filter: DiscoveredDateFilter,
1000
1001    /// Filter by document flags
1002    #[serde(rename = "DocFlagsFilters")]
1003    pub doc_flags_filters: DocFlagsFilters,
1004
1005    /// Filter by HTTP status codes
1006    #[serde(rename = "HttpCodeFilters")]
1007    pub http_code_filters: HttpCodeFilters,
1008}
1009
1010/// URL fetched on demand
1011///
1012/// Result of requesting Bing to fetch a specific URL.
1013///
1014/// Reference: Microsoft.Bing.Webmaster.Api.Interfaces.FetchedUrl
1015#[derive(Debug, Clone, Serialize, Deserialize)]
1016pub struct FetchedUrl {
1017    /// The URL that was fetched
1018    #[serde(rename = "Url")]
1019    pub url: String,
1020
1021    /// When the URL was fetched
1022    #[serde(rename = "Date", with = "dotnet_date_format")]
1023    pub date: NaiveDate,
1024
1025    /// Whether the fetch request has expired
1026    #[serde(rename = "Expired")]
1027    pub expired: bool,
1028
1029    /// Whether the URL was successfully fetched
1030    #[serde(rename = "Fetched")]
1031    pub fetched: bool,
1032}
1033
1034/// Detailed information about a fetched URL
1035///
1036/// Extended version of `FetchedUrl` with document content and HTTP headers.
1037///
1038/// Reference: Microsoft.Bing.Webmaster.Api.Interfaces.FetchedUrlDetails
1039#[derive(Debug, Clone, Serialize, Deserialize)]
1040pub struct FetchedUrlDetails {
1041    /// The URL that was fetched
1042    #[serde(rename = "Url")]
1043    pub url: String,
1044
1045    /// When the URL was fetched
1046    #[serde(rename = "Date", with = "dotnet_date_format")]
1047    pub date: NaiveDate,
1048
1049    /// The document content returned
1050    #[serde(rename = "Document")]
1051    pub document: String,
1052
1053    /// HTTP response headers
1054    #[serde(rename = "Headers")]
1055    pub headers: String,
1056
1057    /// HTTP status message
1058    #[serde(rename = "Status")]
1059    pub status: String,
1060}
1061
1062/// Keyword search statistics
1063///
1064/// Performance metrics for a specific search keyword.
1065///
1066/// Reference: Microsoft.Bing.Webmaster.Api.Interfaces.Keyword
1067#[derive(Debug, Clone, Serialize, Deserialize)]
1068pub struct Keyword {
1069    /// The search query/keyword
1070    #[serde(rename = "Query")]
1071    pub query: String,
1072
1073    /// Number of exact match impressions for this keyword
1074    #[serde(rename = "Impressions")]
1075    pub impressions: i64,
1076
1077    /// Number of broad match impressions for this keyword
1078    #[serde(rename = "BroadImpressions")]
1079    pub broad_impressions: i64,
1080}
1081
1082/// Keyword statistics for a specific date
1083///
1084/// Performance metrics for a keyword on a particular date.
1085///
1086/// Reference: Microsoft.Bing.Webmaster.Api.Interfaces.KeywordStats
1087#[derive(Debug, Clone, Serialize, Deserialize)]
1088pub struct KeywordStats {
1089    /// The search query/keyword
1090    #[serde(rename = "Query")]
1091    pub query: String,
1092
1093    /// Number of exact match impressions
1094    #[serde(rename = "Impressions")]
1095    pub impressions: i64,
1096
1097    /// Number of broad match impressions
1098    #[serde(rename = "BroadImpressions")]
1099    pub broad_impressions: i64,
1100
1101    /// Date of these statistics
1102    #[serde(rename = "Date", with = "dotnet_date_format")]
1103    pub date: NaiveDate,
1104}
1105
1106/// Site migration information
1107///
1108/// Represents a site move/migration from one URL to another.
1109/// Used to inform Bing about domain changes or HTTPS migrations.
1110#[derive(Debug, Clone, Serialize, Deserialize)]
1111pub struct SiteMove {
1112    /// Original site URL (source)
1113    #[serde(rename = "SourceSite")]
1114    pub source_site: String,
1115
1116    /// New site URL (target/destination)
1117    #[serde(rename = "TargetSite")]
1118    pub target_site: String,
1119
1120    /// Date when the site move was registered
1121    #[serde(rename = "Date", with = "dotnet_date_format")]
1122    pub date: NaiveDate,
1123
1124    /// Current status of the site move (e.g., "InProgress", "Complete")
1125    #[serde(rename = "Status")]
1126    pub status: String,
1127}
1128
1129/// Site move configuration settings
1130///
1131/// Settings required to configure a site migration.
1132#[derive(Debug, Clone, Serialize, Deserialize)]
1133pub struct SiteMoveSettings {
1134    /// Target site URL (new location)
1135    #[serde(rename = "TargetSite")]
1136    pub target_site: String,
1137
1138    /// Validation tag to verify ownership of target site
1139    #[serde(rename = "ValidationTag")]
1140    pub validation_tag: String,
1141}