crawlberg 1.7.1 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  // This file is auto-generated by alef. DO NOT EDIT.
2
- // alef:hash:202c4f469a62d77930ef13ac10e1718ac319e02c448ba94573bae7ab48c1d4ce
2
+ // alef:hash:51d55e2247bdc2514829f3eaafb5c8524624f269469856210cf0950e6c206e7f
3
3
  // Re-generate with: alef generate
4
4
  #![allow(dead_code, unused_imports, unused_variables)]
5
5
  #![allow(
@@ -821,7 +821,7 @@ impl BatchScrapeResults {
821
821
  }
822
822
  }
823
823
 
824
- #[derive(Clone, Debug, serde::Serialize)]
824
+ #[derive(Clone, serde::Serialize)]
825
825
  #[serde(default)]
826
826
  #[magnus::wrap(class = "Crawlberg::BrowserConfig")]
827
827
  pub struct BrowserConfig {
@@ -830,6 +830,8 @@ pub struct BrowserConfig {
830
830
  endpoint: Option<String>,
831
831
  #[serde(default = "default_timeout")]
832
832
  timeout: u64,
833
+ overall_timeout: u64,
834
+ shutdown_timeout: u64,
833
835
  wait: BrowserWait,
834
836
  wait_selector: Option<String>,
835
837
  extra_wait: Option<u64>,
@@ -839,6 +841,8 @@ pub struct BrowserConfig {
839
841
  robots_user_agent: Option<String>,
840
842
  capture_network_events: bool,
841
843
  session_affinity: bool,
844
+ chrome_path: Option<String>,
845
+ chrome_args: Vec<String>,
842
846
  }
843
847
 
844
848
  unsafe impl IntoValueFromNative for BrowserConfig {}
@@ -868,6 +872,29 @@ impl magnus::TryConvert for BrowserConfig {
868
872
  }
869
873
 
870
874
  unsafe impl TryConvertOwned for BrowserConfig {}
875
+ impl std::fmt::Debug for BrowserConfig {
876
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
877
+ let mut debug = f.debug_struct("BrowserConfig");
878
+ debug.field("mode", &self.mode);
879
+ debug.field("backend", &self.backend);
880
+ debug.field("endpoint", &"<redacted>");
881
+ debug.field("timeout", &self.timeout);
882
+ debug.field("overall_timeout", &self.overall_timeout);
883
+ debug.field("shutdown_timeout", &self.shutdown_timeout);
884
+ debug.field("wait", &self.wait);
885
+ debug.field("wait_selector", &self.wait_selector);
886
+ debug.field("extra_wait", &self.extra_wait);
887
+ debug.field("proxy", &self.proxy);
888
+ debug.field("block_url_patterns", &self.block_url_patterns);
889
+ debug.field("eval_script", &"<redacted>");
890
+ debug.field("robots_user_agent", &self.robots_user_agent);
891
+ debug.field("capture_network_events", &self.capture_network_events);
892
+ debug.field("session_affinity", &self.session_affinity);
893
+ debug.field("chrome_path", &self.chrome_path);
894
+ debug.field("chrome_args", &"<redacted>");
895
+ debug.finish()
896
+ }
897
+ }
871
898
  impl<'de> serde::Deserialize<'de> for BrowserConfig {
872
899
  fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error>
873
900
  where
@@ -926,6 +953,24 @@ impl BrowserConfig {
926
953
  })?,
927
954
  None => 30000,
928
955
  },
956
+ overall_timeout: match kwargs.get(ruby.to_symbol("overall_timeout")) {
957
+ Some(v) => u64::try_convert(v).map_err(|e| {
958
+ magnus::Error::new(
959
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
960
+ format!("invalid value for `overall_timeout`: {}", e),
961
+ )
962
+ })?,
963
+ None => 60000,
964
+ },
965
+ shutdown_timeout: match kwargs.get(ruby.to_symbol("shutdown_timeout")) {
966
+ Some(v) => u64::try_convert(v).map_err(|e| {
967
+ magnus::Error::new(
968
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
969
+ format!("invalid value for `shutdown_timeout`: {}", e),
970
+ )
971
+ })?,
972
+ None => 5000,
973
+ },
929
974
  wait: match kwargs.get(ruby.to_symbol("wait")) {
930
975
  Some(v) => BrowserWait::try_convert(v).map_err(|e| {
931
976
  magnus::Error::new(
@@ -1007,6 +1052,24 @@ impl BrowserConfig {
1007
1052
  })?,
1008
1053
  None => true,
1009
1054
  },
1055
+ chrome_path: match kwargs.get(ruby.to_symbol("chrome_path")).filter(|v| !v.is_nil()) {
1056
+ Some(v) => Some(String::try_convert(v).map_err(|e| {
1057
+ magnus::Error::new(
1058
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
1059
+ format!("invalid value for `chrome_path`: {}", e),
1060
+ )
1061
+ })?),
1062
+ None => None,
1063
+ },
1064
+ chrome_args: match kwargs.get(ruby.to_symbol("chrome_args")) {
1065
+ Some(v) => <Vec<String>>::try_convert(v).map_err(|e| {
1066
+ magnus::Error::new(
1067
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
1068
+ format!("invalid value for `chrome_args`: {}", e),
1069
+ )
1070
+ })?,
1071
+ None => Default::default(),
1072
+ },
1010
1073
  })
1011
1074
  }
1012
1075
 
@@ -1026,6 +1089,14 @@ impl BrowserConfig {
1026
1089
  self.timeout.clone()
1027
1090
  }
1028
1091
 
1092
+ fn overall_timeout(&self) -> u64 {
1093
+ self.overall_timeout.clone()
1094
+ }
1095
+
1096
+ fn shutdown_timeout(&self) -> u64 {
1097
+ self.shutdown_timeout.clone()
1098
+ }
1099
+
1029
1100
  fn wait(&self) -> BrowserWait {
1030
1101
  self.wait.clone()
1031
1102
  }
@@ -1061,6 +1132,14 @@ impl BrowserConfig {
1061
1132
  fn session_affinity(&self) -> bool {
1062
1133
  self.session_affinity
1063
1134
  }
1135
+
1136
+ fn chrome_path(&self) -> Option<String> {
1137
+ self.chrome_path.clone()
1138
+ }
1139
+
1140
+ fn chrome_args(&self) -> Vec<String> {
1141
+ self.chrome_args.clone()
1142
+ }
1064
1143
  }
1065
1144
 
1066
1145
  #[derive(Clone, Debug, serde::Serialize)]
@@ -1357,6 +1436,7 @@ pub struct ContentConfig {
1357
1436
  wrap: bool,
1358
1437
  wrap_width: usize,
1359
1438
  include_document_structure: bool,
1439
+ extract_metadata: bool,
1360
1440
  }
1361
1441
 
1362
1442
  unsafe impl IntoValueFromNative for ContentConfig {}
@@ -1516,6 +1596,15 @@ impl ContentConfig {
1516
1596
  })?,
1517
1597
  None => true,
1518
1598
  },
1599
+ extract_metadata: match kwargs.get(ruby.to_symbol("extract_metadata")) {
1600
+ Some(v) => bool::try_convert(v).map_err(|e| {
1601
+ magnus::Error::new(
1602
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
1603
+ format!("invalid value for `extract_metadata`: {}", e),
1604
+ )
1605
+ })?,
1606
+ None => true,
1607
+ },
1519
1608
  })
1520
1609
  }
1521
1610
 
@@ -1566,9 +1655,13 @@ impl ContentConfig {
1566
1655
  fn include_document_structure(&self) -> bool {
1567
1656
  self.include_document_structure
1568
1657
  }
1658
+
1659
+ fn extract_metadata(&self) -> bool {
1660
+ self.extract_metadata
1661
+ }
1569
1662
  }
1570
1663
 
1571
- #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
1664
+ #[derive(Clone, serde::Serialize, serde::Deserialize)]
1572
1665
  #[serde(default)]
1573
1666
  #[magnus::wrap(class = "Crawlberg::CookieInfo")]
1574
1667
  pub struct CookieInfo {
@@ -1605,6 +1698,16 @@ impl magnus::TryConvert for CookieInfo {
1605
1698
  }
1606
1699
 
1607
1700
  unsafe impl TryConvertOwned for CookieInfo {}
1701
+ impl std::fmt::Debug for CookieInfo {
1702
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1703
+ let mut debug = f.debug_struct("CookieInfo");
1704
+ debug.field("name", &self.name);
1705
+ debug.field("value", &"<redacted>");
1706
+ debug.field("domain", &self.domain);
1707
+ debug.field("path", &self.path);
1708
+ debug.finish()
1709
+ }
1710
+ }
1608
1711
 
1609
1712
  impl Default for CookieInfo {
1610
1713
  fn default() -> Self {
@@ -1675,7 +1778,7 @@ impl CookieInfo {
1675
1778
  }
1676
1779
  }
1677
1780
 
1678
- #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
1781
+ #[derive(Clone, serde::Serialize, serde::Deserialize)]
1679
1782
  #[serde(default)]
1680
1783
  #[magnus::wrap(class = "Crawlberg::CrawlConfig")]
1681
1784
  pub struct CrawlConfig {
@@ -1694,6 +1797,11 @@ pub struct CrawlConfig {
1694
1797
  allow_subdomains: bool,
1695
1798
  include_paths: Vec<String>,
1696
1799
  exclude_paths: Vec<String>,
1800
+ path_patterns_match_query: bool,
1801
+ path_patterns_match_url: bool,
1802
+ dedup_include_query: bool,
1803
+ strip_tracking_params: bool,
1804
+ tracking_params: Vec<String>,
1697
1805
  custom_headers: HashMap<String, String>,
1698
1806
  #[serde(default = "default_timeout")]
1699
1807
  request_timeout: u64,
@@ -1701,6 +1809,9 @@ pub struct CrawlConfig {
1701
1809
  max_redirects: usize,
1702
1810
  retry_count: usize,
1703
1811
  retry_codes: Vec<u16>,
1812
+ retry_initial_delay_ms: u64,
1813
+ retry_max_delay_ms: u64,
1814
+ rate_limit_jitter_ratio: f64,
1704
1815
  cookies_enabled: bool,
1705
1816
  auth: Option<AuthConfig>,
1706
1817
  max_body_size: Option<usize>,
@@ -1756,6 +1867,67 @@ impl magnus::TryConvert for CrawlConfig {
1756
1867
  }
1757
1868
 
1758
1869
  unsafe impl TryConvertOwned for CrawlConfig {}
1870
+ impl std::fmt::Debug for CrawlConfig {
1871
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1872
+ let mut debug = f.debug_struct("CrawlConfig");
1873
+ debug.field("max_depth", &self.max_depth);
1874
+ debug.field("max_pages", &self.max_pages);
1875
+ debug.field("max_links_per_page", &self.max_links_per_page);
1876
+ debug.field("max_concurrent", &self.max_concurrent);
1877
+ debug.field("crawl_strategy", &self.crawl_strategy);
1878
+ debug.field("content_filter", &self.content_filter);
1879
+ debug.field("bm25_query", &self.bm25_query);
1880
+ debug.field("bm25_threshold", &self.bm25_threshold);
1881
+ debug.field("respect_robots_txt", &self.respect_robots_txt);
1882
+ debug.field("soft_http_errors", &self.soft_http_errors);
1883
+ debug.field("user_agent", &self.user_agent);
1884
+ debug.field("stay_on_domain", &self.stay_on_domain);
1885
+ debug.field("allow_subdomains", &self.allow_subdomains);
1886
+ debug.field("include_paths", &self.include_paths);
1887
+ debug.field("exclude_paths", &self.exclude_paths);
1888
+ debug.field("path_patterns_match_query", &self.path_patterns_match_query);
1889
+ debug.field("path_patterns_match_url", &self.path_patterns_match_url);
1890
+ debug.field("dedup_include_query", &self.dedup_include_query);
1891
+ debug.field("strip_tracking_params", &self.strip_tracking_params);
1892
+ debug.field("tracking_params", &self.tracking_params);
1893
+ debug.field("custom_headers", &"<redacted>");
1894
+ debug.field("request_timeout", &self.request_timeout);
1895
+ debug.field("rate_limit_ms", &self.rate_limit_ms);
1896
+ debug.field("max_redirects", &self.max_redirects);
1897
+ debug.field("retry_count", &self.retry_count);
1898
+ debug.field("retry_codes", &self.retry_codes);
1899
+ debug.field("retry_initial_delay_ms", &self.retry_initial_delay_ms);
1900
+ debug.field("retry_max_delay_ms", &self.retry_max_delay_ms);
1901
+ debug.field("rate_limit_jitter_ratio", &self.rate_limit_jitter_ratio);
1902
+ debug.field("cookies_enabled", &self.cookies_enabled);
1903
+ debug.field("auth", &self.auth);
1904
+ debug.field("max_body_size", &self.max_body_size);
1905
+ debug.field("remove_tags", &self.remove_tags);
1906
+ debug.field("content", &self.content);
1907
+ debug.field("map_limit", &self.map_limit);
1908
+ debug.field("map_search", &self.map_search);
1909
+ debug.field("download_assets", &self.download_assets);
1910
+ debug.field("asset_types", &self.asset_types);
1911
+ debug.field("max_asset_size", &self.max_asset_size);
1912
+ debug.field("browser", &self.browser);
1913
+ debug.field("proxy", &self.proxy);
1914
+ debug.field("user_agents", &self.user_agents);
1915
+ debug.field("capture_screenshot", &self.capture_screenshot);
1916
+ debug.field("follow_document_urls", &self.follow_document_urls);
1917
+ debug.field("document_url_depth", &self.document_url_depth);
1918
+ debug.field("download_documents", &self.download_documents);
1919
+ debug.field("document_max_size", &self.document_max_size);
1920
+ debug.field("document_mime_types", &self.document_mime_types);
1921
+ debug.field("document_output_dir", &self.document_output_dir);
1922
+ debug.field("document_content_encoding", &self.document_content_encoding);
1923
+ debug.field("warc_output", &self.warc_output);
1924
+ debug.field("browser_profile", &self.browser_profile);
1925
+ debug.field("save_browser_profile", &self.save_browser_profile);
1926
+ debug.field("ssrf", &self.ssrf);
1927
+ debug.field("ssrf_deny_private_explicit", &self.ssrf_deny_private_explicit);
1928
+ debug.finish()
1929
+ }
1930
+ }
1759
1931
 
1760
1932
  impl Default for CrawlConfig {
1761
1933
  fn default() -> Self {
@@ -1905,6 +2077,56 @@ impl CrawlConfig {
1905
2077
  })?,
1906
2078
  None => Default::default(),
1907
2079
  },
2080
+ path_patterns_match_query: match kwargs.get(ruby.to_symbol("path_patterns_match_query")) {
2081
+ Some(v) => bool::try_convert(v).map_err(|e| {
2082
+ magnus::Error::new(
2083
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
2084
+ format!("invalid value for `path_patterns_match_query`: {}", e),
2085
+ )
2086
+ })?,
2087
+ None => false,
2088
+ },
2089
+ path_patterns_match_url: match kwargs.get(ruby.to_symbol("path_patterns_match_url")) {
2090
+ Some(v) => bool::try_convert(v).map_err(|e| {
2091
+ magnus::Error::new(
2092
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
2093
+ format!("invalid value for `path_patterns_match_url`: {}", e),
2094
+ )
2095
+ })?,
2096
+ None => false,
2097
+ },
2098
+ dedup_include_query: match kwargs.get(ruby.to_symbol("dedup_include_query")) {
2099
+ Some(v) => bool::try_convert(v).map_err(|e| {
2100
+ magnus::Error::new(
2101
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
2102
+ format!("invalid value for `dedup_include_query`: {}", e),
2103
+ )
2104
+ })?,
2105
+ None => false,
2106
+ },
2107
+ strip_tracking_params: match kwargs.get(ruby.to_symbol("strip_tracking_params")) {
2108
+ Some(v) => bool::try_convert(v).map_err(|e| {
2109
+ magnus::Error::new(
2110
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
2111
+ format!("invalid value for `strip_tracking_params`: {}", e),
2112
+ )
2113
+ })?,
2114
+ None => false,
2115
+ },
2116
+ tracking_params: match kwargs.get(ruby.to_symbol("tracking_params")) {
2117
+ Some(v) => <Vec<String>>::try_convert(v).map_err(|e| {
2118
+ magnus::Error::new(
2119
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
2120
+ format!("invalid value for `tracking_params`: {}", e),
2121
+ )
2122
+ })?,
2123
+ None => vec![
2124
+ "utm_*".to_string(),
2125
+ "fbclid".to_string(),
2126
+ "gclid".to_string(),
2127
+ "ref".to_string(),
2128
+ ],
2129
+ },
1908
2130
  custom_headers: match kwargs.get(ruby.to_symbol("custom_headers")) {
1909
2131
  Some(v) => <HashMap<String, String>>::try_convert(v).map_err(|e| {
1910
2132
  magnus::Error::new(
@@ -1959,6 +2181,33 @@ impl CrawlConfig {
1959
2181
  })?,
1960
2182
  None => Default::default(),
1961
2183
  },
2184
+ retry_initial_delay_ms: match kwargs.get(ruby.to_symbol("retry_initial_delay_ms")) {
2185
+ Some(v) => u64::try_convert(v).map_err(|e| {
2186
+ magnus::Error::new(
2187
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
2188
+ format!("invalid value for `retry_initial_delay_ms`: {}", e),
2189
+ )
2190
+ })?,
2191
+ None => 100,
2192
+ },
2193
+ retry_max_delay_ms: match kwargs.get(ruby.to_symbol("retry_max_delay_ms")) {
2194
+ Some(v) => u64::try_convert(v).map_err(|e| {
2195
+ magnus::Error::new(
2196
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
2197
+ format!("invalid value for `retry_max_delay_ms`: {}", e),
2198
+ )
2199
+ })?,
2200
+ None => 60000,
2201
+ },
2202
+ rate_limit_jitter_ratio: match kwargs.get(ruby.to_symbol("rate_limit_jitter_ratio")) {
2203
+ Some(v) => f64::try_convert(v).map_err(|e| {
2204
+ magnus::Error::new(
2205
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
2206
+ format!("invalid value for `rate_limit_jitter_ratio`: {}", e),
2207
+ )
2208
+ })?,
2209
+ None => 0.0,
2210
+ },
1962
2211
  cookies_enabled: match kwargs.get(ruby.to_symbol("cookies_enabled")) {
1963
2212
  Some(v) => bool::try_convert(v).map_err(|e| {
1964
2213
  magnus::Error::new(
@@ -2265,6 +2514,26 @@ impl CrawlConfig {
2265
2514
  self.exclude_paths.clone()
2266
2515
  }
2267
2516
 
2517
+ fn path_patterns_match_query(&self) -> bool {
2518
+ self.path_patterns_match_query
2519
+ }
2520
+
2521
+ fn path_patterns_match_url(&self) -> bool {
2522
+ self.path_patterns_match_url
2523
+ }
2524
+
2525
+ fn dedup_include_query(&self) -> bool {
2526
+ self.dedup_include_query
2527
+ }
2528
+
2529
+ fn strip_tracking_params(&self) -> bool {
2530
+ self.strip_tracking_params
2531
+ }
2532
+
2533
+ fn tracking_params(&self) -> Vec<String> {
2534
+ self.tracking_params.clone()
2535
+ }
2536
+
2268
2537
  fn custom_headers(&self) -> HashMap<String, String> {
2269
2538
  self.custom_headers.clone()
2270
2539
  }
@@ -2289,6 +2558,18 @@ impl CrawlConfig {
2289
2558
  self.retry_codes.clone()
2290
2559
  }
2291
2560
 
2561
+ fn retry_initial_delay_ms(&self) -> u64 {
2562
+ self.retry_initial_delay_ms
2563
+ }
2564
+
2565
+ fn retry_max_delay_ms(&self) -> u64 {
2566
+ self.retry_max_delay_ms
2567
+ }
2568
+
2569
+ fn rate_limit_jitter_ratio(&self) -> f64 {
2570
+ self.rate_limit_jitter_ratio
2571
+ }
2572
+
2292
2573
  fn cookies_enabled(&self) -> bool {
2293
2574
  self.cookies_enabled
2294
2575
  }
@@ -2426,6 +2707,16 @@ impl CrawlConfig {
2426
2707
 
2427
2708
  exclude_paths: self.exclude_paths.clone(),
2428
2709
 
2710
+ path_patterns_match_query: self.path_patterns_match_query,
2711
+
2712
+ path_patterns_match_url: self.path_patterns_match_url,
2713
+
2714
+ dedup_include_query: self.dedup_include_query,
2715
+
2716
+ strip_tracking_params: self.strip_tracking_params,
2717
+
2718
+ tracking_params: self.tracking_params.clone(),
2719
+
2429
2720
  custom_headers: self
2430
2721
  .custom_headers
2431
2722
  .clone()
@@ -2443,6 +2734,12 @@ impl CrawlConfig {
2443
2734
 
2444
2735
  retry_codes: self.retry_codes.clone(),
2445
2736
 
2737
+ retry_initial_delay_ms: self.retry_initial_delay_ms,
2738
+
2739
+ retry_max_delay_ms: self.retry_max_delay_ms,
2740
+
2741
+ rate_limit_jitter_ratio: self.rate_limit_jitter_ratio,
2742
+
2446
2743
  cookies_enabled: self.cookies_enabled,
2447
2744
 
2448
2745
  auth: self.auth.clone().map(Into::into),
@@ -2619,7 +2916,7 @@ impl CrawlEngineHandle {
2619
2916
  }
2620
2917
  }
2621
2918
 
2622
- #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
2919
+ #[derive(Clone, Debug, serde::Serialize)]
2623
2920
  #[serde(default)]
2624
2921
  #[magnus::wrap(class = "Crawlberg::CrawlPageResult")]
2625
2922
  pub struct CrawlPageResult {
@@ -2644,6 +2941,11 @@ pub struct CrawlPageResult {
2644
2941
  extraction_meta: Option<ExtractionMeta>,
2645
2942
  downloaded_document: Option<DownloadedDocument>,
2646
2943
  browser_used: bool,
2944
+ final_url: String,
2945
+ redirect_count: usize,
2946
+ noindex_detected: bool,
2947
+ nofollow_detected: bool,
2948
+ ssrf_refused_urls: Vec<String>,
2647
2949
  }
2648
2950
 
2649
2951
  unsafe impl IntoValueFromNative for CrawlPageResult {}
@@ -2673,6 +2975,14 @@ impl magnus::TryConvert for CrawlPageResult {
2673
2975
  }
2674
2976
 
2675
2977
  unsafe impl TryConvertOwned for CrawlPageResult {}
2978
+ impl<'de> serde::Deserialize<'de> for CrawlPageResult {
2979
+ fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error>
2980
+ where
2981
+ D: serde::Deserializer<'de>,
2982
+ {
2983
+ <crawlberg::CrawlPageResult as serde::Deserialize>::deserialize(deserializer).map(Into::into)
2984
+ }
2985
+ }
2676
2986
 
2677
2987
  impl Default for CrawlPageResult {
2678
2988
  fn default() -> Self {
@@ -2879,6 +3189,51 @@ impl CrawlPageResult {
2879
3189
  })?,
2880
3190
  None => Default::default(),
2881
3191
  },
3192
+ final_url: match kwargs.get(ruby.to_symbol("final_url")) {
3193
+ Some(v) => String::try_convert(v).map_err(|e| {
3194
+ magnus::Error::new(
3195
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
3196
+ format!("invalid value for `final_url`: {}", e),
3197
+ )
3198
+ })?,
3199
+ None => Default::default(),
3200
+ },
3201
+ redirect_count: match kwargs.get(ruby.to_symbol("redirect_count")) {
3202
+ Some(v) => usize::try_convert(v).map_err(|e| {
3203
+ magnus::Error::new(
3204
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
3205
+ format!("invalid value for `redirect_count`: {}", e),
3206
+ )
3207
+ })?,
3208
+ None => Default::default(),
3209
+ },
3210
+ noindex_detected: match kwargs.get(ruby.to_symbol("noindex_detected")) {
3211
+ Some(v) => bool::try_convert(v).map_err(|e| {
3212
+ magnus::Error::new(
3213
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
3214
+ format!("invalid value for `noindex_detected`: {}", e),
3215
+ )
3216
+ })?,
3217
+ None => Default::default(),
3218
+ },
3219
+ nofollow_detected: match kwargs.get(ruby.to_symbol("nofollow_detected")) {
3220
+ Some(v) => bool::try_convert(v).map_err(|e| {
3221
+ magnus::Error::new(
3222
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
3223
+ format!("invalid value for `nofollow_detected`: {}", e),
3224
+ )
3225
+ })?,
3226
+ None => Default::default(),
3227
+ },
3228
+ ssrf_refused_urls: match kwargs.get(ruby.to_symbol("ssrf_refused_urls")) {
3229
+ Some(v) => <Vec<String>>::try_convert(v).map_err(|e| {
3230
+ magnus::Error::new(
3231
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
3232
+ format!("invalid value for `ssrf_refused_urls`: {}", e),
3233
+ )
3234
+ })?,
3235
+ None => Default::default(),
3236
+ },
2882
3237
  })
2883
3238
  }
2884
3239
 
@@ -2965,6 +3320,26 @@ impl CrawlPageResult {
2965
3320
  fn browser_used(&self) -> bool {
2966
3321
  self.browser_used
2967
3322
  }
3323
+
3324
+ fn final_url(&self) -> String {
3325
+ self.final_url.clone()
3326
+ }
3327
+
3328
+ fn redirect_count(&self) -> usize {
3329
+ self.redirect_count
3330
+ }
3331
+
3332
+ fn noindex_detected(&self) -> bool {
3333
+ self.noindex_detected
3334
+ }
3335
+
3336
+ fn nofollow_detected(&self) -> bool {
3337
+ self.nofollow_detected
3338
+ }
3339
+
3340
+ fn ssrf_refused_urls(&self) -> Vec<String> {
3341
+ self.ssrf_refused_urls.clone()
3342
+ }
2968
3343
  }
2969
3344
 
2970
3345
  #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -3360,7 +3735,7 @@ impl DownloadedAsset {
3360
3735
  }
3361
3736
  }
3362
3737
 
3363
- #[derive(Clone, Debug, serde::Serialize)]
3738
+ #[derive(Clone, serde::Serialize)]
3364
3739
  #[serde(default)]
3365
3740
  #[magnus::wrap(class = "Crawlberg::DownloadedDocument")]
3366
3741
  pub struct DownloadedDocument {
@@ -3402,6 +3777,21 @@ impl magnus::TryConvert for DownloadedDocument {
3402
3777
  }
3403
3778
 
3404
3779
  unsafe impl TryConvertOwned for DownloadedDocument {}
3780
+ impl std::fmt::Debug for DownloadedDocument {
3781
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3782
+ let mut debug = f.debug_struct("DownloadedDocument");
3783
+ debug.field("url", &self.url);
3784
+ debug.field("mime_type", &self.mime_type);
3785
+ debug.field("size", &self.size);
3786
+ debug.field("filename", &self.filename);
3787
+ debug.field("content_hash", &self.content_hash);
3788
+ debug.field("headers", &"<redacted>");
3789
+ debug.field("truncated", &self.truncated);
3790
+ debug.field("content_path", &self.content_path);
3791
+ debug.field("content_base64", &self.content_base64);
3792
+ debug.finish()
3793
+ }
3794
+ }
3405
3795
  impl<'de> serde::Deserialize<'de> for DownloadedDocument {
3406
3796
  fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error>
3407
3797
  where
@@ -4145,7 +4535,7 @@ impl ImageInfo {
4145
4535
  }
4146
4536
  }
4147
4537
 
4148
- #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
4538
+ #[derive(Clone, Debug, serde::Serialize)]
4149
4539
  #[serde(default)]
4150
4540
  #[magnus::wrap(class = "Crawlberg::InteractionResult")]
4151
4541
  pub struct InteractionResult {
@@ -4153,6 +4543,7 @@ pub struct InteractionResult {
4153
4543
  final_html: String,
4154
4544
  final_url: String,
4155
4545
  screenshot_base64: Option<String>,
4546
+ ssrf_refused_urls: Vec<String>,
4156
4547
  }
4157
4548
 
4158
4549
  unsafe impl IntoValueFromNative for InteractionResult {}
@@ -4182,6 +4573,14 @@ impl magnus::TryConvert for InteractionResult {
4182
4573
  }
4183
4574
 
4184
4575
  unsafe impl TryConvertOwned for InteractionResult {}
4576
+ impl<'de> serde::Deserialize<'de> for InteractionResult {
4577
+ fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error>
4578
+ where
4579
+ D: serde::Deserializer<'de>,
4580
+ {
4581
+ <crawlberg::InteractionResult as serde::Deserialize>::deserialize(deserializer).map(Into::into)
4582
+ }
4583
+ }
4185
4584
 
4186
4585
  impl Default for InteractionResult {
4187
4586
  fn default() -> Self {
@@ -4232,6 +4631,15 @@ impl InteractionResult {
4232
4631
  })?),
4233
4632
  None => None,
4234
4633
  },
4634
+ ssrf_refused_urls: match kwargs.get(ruby.to_symbol("ssrf_refused_urls")) {
4635
+ Some(v) => <Vec<String>>::try_convert(v).map_err(|e| {
4636
+ magnus::Error::new(
4637
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
4638
+ format!("invalid value for `ssrf_refused_urls`: {}", e),
4639
+ )
4640
+ })?,
4641
+ None => Default::default(),
4642
+ },
4235
4643
  })
4236
4644
  }
4237
4645
 
@@ -4250,6 +4658,10 @@ impl InteractionResult {
4250
4658
  fn screenshot_base64(&self) -> Option<String> {
4251
4659
  self.screenshot_base64.clone()
4252
4660
  }
4661
+
4662
+ fn ssrf_refused_urls(&self) -> Vec<String> {
4663
+ self.ssrf_refused_urls.clone()
4664
+ }
4253
4665
  }
4254
4666
 
4255
4667
  #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -5330,7 +5742,7 @@ impl PageMetadata {
5330
5742
  }
5331
5743
  }
5332
5744
 
5333
- #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
5745
+ #[derive(Clone, serde::Serialize, serde::Deserialize)]
5334
5746
  #[serde(default)]
5335
5747
  #[magnus::wrap(class = "Crawlberg::ProxyConfig")]
5336
5748
  pub struct ProxyConfig {
@@ -5366,6 +5778,15 @@ impl magnus::TryConvert for ProxyConfig {
5366
5778
  }
5367
5779
 
5368
5780
  unsafe impl TryConvertOwned for ProxyConfig {}
5781
+ impl std::fmt::Debug for ProxyConfig {
5782
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5783
+ let mut debug = f.debug_struct("ProxyConfig");
5784
+ debug.field("url", &"<redacted>");
5785
+ debug.field("username", &self.username);
5786
+ debug.field("password", &"<redacted>");
5787
+ debug.finish()
5788
+ }
5789
+ }
5369
5790
 
5370
5791
  impl Default for ProxyConfig {
5371
5792
  fn default() -> Self {
@@ -5572,7 +5993,7 @@ impl ResponseMeta {
5572
5993
  }
5573
5994
  }
5574
5995
 
5575
- #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
5996
+ #[derive(Clone, Debug, serde::Serialize)]
5576
5997
  #[serde(default)]
5577
5998
  #[magnus::wrap(class = "Crawlberg::ScrapeResult")]
5578
5999
  pub struct ScrapeResult {
@@ -5605,6 +6026,7 @@ pub struct ScrapeResult {
5605
6026
  screenshot_base64: Option<String>,
5606
6027
  downloaded_document: Option<DownloadedDocument>,
5607
6028
  browser: Option<BrowserExtras>,
6029
+ ssrf_refused_urls: Vec<String>,
5608
6030
  }
5609
6031
 
5610
6032
  unsafe impl IntoValueFromNative for ScrapeResult {}
@@ -5634,6 +6056,14 @@ impl magnus::TryConvert for ScrapeResult {
5634
6056
  }
5635
6057
 
5636
6058
  unsafe impl TryConvertOwned for ScrapeResult {}
6059
+ impl<'de> serde::Deserialize<'de> for ScrapeResult {
6060
+ fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error>
6061
+ where
6062
+ D: serde::Deserializer<'de>,
6063
+ {
6064
+ <crawlberg::ScrapeResult as serde::Deserialize>::deserialize(deserializer).map(Into::into)
6065
+ }
6066
+ }
5637
6067
 
5638
6068
  impl Default for ScrapeResult {
5639
6069
  fn default() -> Self {
@@ -5912,6 +6342,15 @@ impl ScrapeResult {
5912
6342
  })?),
5913
6343
  None => None,
5914
6344
  },
6345
+ ssrf_refused_urls: match kwargs.get(ruby.to_symbol("ssrf_refused_urls")) {
6346
+ Some(v) => <Vec<String>>::try_convert(v).map_err(|e| {
6347
+ magnus::Error::new(
6348
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
6349
+ format!("invalid value for `ssrf_refused_urls`: {}", e),
6350
+ )
6351
+ })?,
6352
+ None => Default::default(),
6353
+ },
5915
6354
  })
5916
6355
  }
5917
6356
 
@@ -6030,6 +6469,10 @@ impl ScrapeResult {
6030
6469
  fn browser(&self) -> Option<BrowserExtras> {
6031
6470
  self.browser.clone()
6032
6471
  }
6472
+
6473
+ fn ssrf_refused_urls(&self) -> Vec<String> {
6474
+ self.ssrf_refused_urls.clone()
6475
+ }
6033
6476
  }
6034
6477
 
6035
6478
  #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -6145,6 +6588,7 @@ impl SitemapUrl {
6145
6588
  pub struct SsrfPolicy {
6146
6589
  deny_private: bool,
6147
6590
  allowlist: Vec<HostMatcher>,
6591
+ denylist: Vec<HostMatcher>,
6148
6592
  max_redirects: u8,
6149
6593
  scheme_allowlist: Vec<String>,
6150
6594
  }
@@ -6216,6 +6660,15 @@ impl SsrfPolicy {
6216
6660
  })?,
6217
6661
  None => Default::default(),
6218
6662
  },
6663
+ denylist: match kwargs.get(ruby.to_symbol("denylist")) {
6664
+ Some(v) => <Vec<HostMatcher>>::try_convert(v).map_err(|e| {
6665
+ magnus::Error::new(
6666
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
6667
+ format!("invalid value for `denylist`: {}", e),
6668
+ )
6669
+ })?,
6670
+ None => Default::default(),
6671
+ },
6219
6672
  max_redirects: match kwargs.get(ruby.to_symbol("max_redirects")) {
6220
6673
  Some(v) => u8::try_convert(v).map_err(|e| {
6221
6674
  magnus::Error::new(
@@ -6245,6 +6698,10 @@ impl SsrfPolicy {
6245
6698
  self.allowlist.clone()
6246
6699
  }
6247
6700
 
6701
+ fn denylist(&self) -> Vec<HostMatcher> {
6702
+ self.denylist.clone()
6703
+ }
6704
+
6248
6705
  fn max_redirects(&self) -> u8 {
6249
6706
  self.max_redirects
6250
6707
  }
@@ -6254,7 +6711,7 @@ impl SsrfPolicy {
6254
6711
  }
6255
6712
  }
6256
6713
 
6257
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
6714
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
6258
6715
  #[serde(rename_all = "snake_case")]
6259
6716
  pub enum AssetCategory {
6260
6717
  Document,
@@ -6295,7 +6752,14 @@ impl magnus::IntoValue for AssetCategory {
6295
6752
 
6296
6753
  impl magnus::TryConvert for AssetCategory {
6297
6754
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
6298
- let s: String = magnus::TryConvert::try_convert(val)?;
6755
+ // IntoValue emits Symbols, including when this enum is a Hash key.
6756
+ // Accept that exact representation as well as String inputs; do not
6757
+ // coerce arbitrary objects through to_s.
6758
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
6759
+ symbol.name()?.into_owned()
6760
+ } else {
6761
+ magnus::TryConvert::try_convert(val)?
6762
+ };
6299
6763
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
6300
6764
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
6301
6765
  // existing consumer code written in any of those styles keep working.
@@ -6321,7 +6785,7 @@ impl magnus::TryConvert for AssetCategory {
6321
6785
  unsafe impl IntoValueFromNative for AssetCategory {}
6322
6786
  unsafe impl TryConvertOwned for AssetCategory {}
6323
6787
 
6324
- #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
6788
+ #[derive(Clone, serde::Serialize, serde::Deserialize)]
6325
6789
  #[serde(tag = "type")]
6326
6790
  pub enum AuthConfig {
6327
6791
  #[serde(rename = "basic")]
@@ -6352,7 +6816,8 @@ impl magnus::IntoValue for AuthConfig {
6352
6816
 
6353
6817
  impl magnus::TryConvert for AuthConfig {
6354
6818
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
6355
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
6819
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
6820
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
6356
6821
  // For unit enums or when passed as a string, fall back to string-based conversion.
6357
6822
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
6358
6823
  s
@@ -6384,9 +6849,15 @@ impl magnus::TryConvert for AuthConfig {
6384
6849
 
6385
6850
  unsafe impl IntoValueFromNative for AuthConfig {}
6386
6851
  unsafe impl TryConvertOwned for AuthConfig {}
6852
+
6387
6853
  impl AuthConfig {}
6854
+ impl std::fmt::Debug for AuthConfig {
6855
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6856
+ f.write_str("AuthConfig(<redacted>)")
6857
+ }
6858
+ }
6388
6859
 
6389
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
6860
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
6390
6861
  #[serde(rename_all = "snake_case")]
6391
6862
  pub enum BrowserBackend {
6392
6863
  Chromiumoxide,
@@ -6411,7 +6882,14 @@ impl magnus::IntoValue for BrowserBackend {
6411
6882
 
6412
6883
  impl magnus::TryConvert for BrowserBackend {
6413
6884
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
6414
- let s: String = magnus::TryConvert::try_convert(val)?;
6885
+ // IntoValue emits Symbols, including when this enum is a Hash key.
6886
+ // Accept that exact representation as well as String inputs; do not
6887
+ // coerce arbitrary objects through to_s.
6888
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
6889
+ symbol.name()?.into_owned()
6890
+ } else {
6891
+ magnus::TryConvert::try_convert(val)?
6892
+ };
6415
6893
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
6416
6894
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
6417
6895
  // existing consumer code written in any of those styles keep working.
@@ -6429,7 +6907,7 @@ impl magnus::TryConvert for BrowserBackend {
6429
6907
  unsafe impl IntoValueFromNative for BrowserBackend {}
6430
6908
  unsafe impl TryConvertOwned for BrowserBackend {}
6431
6909
 
6432
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
6910
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
6433
6911
  #[serde(rename_all = "snake_case")]
6434
6912
  pub enum BrowserMode {
6435
6913
  Auto,
@@ -6458,7 +6936,14 @@ impl magnus::IntoValue for BrowserMode {
6458
6936
 
6459
6937
  impl magnus::TryConvert for BrowserMode {
6460
6938
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
6461
- let s: String = magnus::TryConvert::try_convert(val)?;
6939
+ // IntoValue emits Symbols, including when this enum is a Hash key.
6940
+ // Accept that exact representation as well as String inputs; do not
6941
+ // coerce arbitrary objects through to_s.
6942
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
6943
+ symbol.name()?.into_owned()
6944
+ } else {
6945
+ magnus::TryConvert::try_convert(val)?
6946
+ };
6462
6947
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
6463
6948
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
6464
6949
  // existing consumer code written in any of those styles keep working.
@@ -6478,7 +6963,7 @@ impl magnus::TryConvert for BrowserMode {
6478
6963
  unsafe impl IntoValueFromNative for BrowserMode {}
6479
6964
  unsafe impl TryConvertOwned for BrowserMode {}
6480
6965
 
6481
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
6966
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
6482
6967
  #[serde(rename_all = "snake_case")]
6483
6968
  pub enum BrowserWait {
6484
6969
  NetworkIdle,
@@ -6505,7 +6990,14 @@ impl magnus::IntoValue for BrowserWait {
6505
6990
 
6506
6991
  impl magnus::TryConvert for BrowserWait {
6507
6992
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
6508
- let s: String = magnus::TryConvert::try_convert(val)?;
6993
+ // IntoValue emits Symbols, including when this enum is a Hash key.
6994
+ // Accept that exact representation as well as String inputs; do not
6995
+ // coerce arbitrary objects through to_s.
6996
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
6997
+ symbol.name()?.into_owned()
6998
+ } else {
6999
+ magnus::TryConvert::try_convert(val)?
7000
+ };
6509
7001
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
6510
7002
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
6511
7003
  // existing consumer code written in any of those styles keep working.
@@ -6524,7 +7016,7 @@ impl magnus::TryConvert for BrowserWait {
6524
7016
  unsafe impl IntoValueFromNative for BrowserWait {}
6525
7017
  unsafe impl TryConvertOwned for BrowserWait {}
6526
7018
 
6527
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
7019
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
6528
7020
  #[serde(rename_all = "snake_case")]
6529
7021
  pub enum ContentFilterKind {
6530
7022
  Bm25,
@@ -6547,7 +7039,14 @@ impl magnus::IntoValue for ContentFilterKind {
6547
7039
 
6548
7040
  impl magnus::TryConvert for ContentFilterKind {
6549
7041
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
6550
- let s: String = magnus::TryConvert::try_convert(val)?;
7042
+ // IntoValue emits Symbols, including when this enum is a Hash key.
7043
+ // Accept that exact representation as well as String inputs; do not
7044
+ // coerce arbitrary objects through to_s.
7045
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
7046
+ symbol.name()?.into_owned()
7047
+ } else {
7048
+ magnus::TryConvert::try_convert(val)?
7049
+ };
6551
7050
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
6552
7051
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
6553
7052
  // existing consumer code written in any of those styles keep working.
@@ -6568,7 +7067,7 @@ unsafe impl TryConvertOwned for ContentFilterKind {}
6568
7067
  #[serde(tag = "type")]
6569
7068
  #[serde(rename_all = "snake_case")]
6570
7069
  pub enum CrawlEvent {
6571
- Page { result: CrawlPageResult },
7070
+ Page { result: Box<CrawlPageResult> },
6572
7071
  Error { url: String, error: String },
6573
7072
  Complete { pages_crawled: usize },
6574
7073
  }
@@ -6592,7 +7091,8 @@ impl magnus::IntoValue for CrawlEvent {
6592
7091
 
6593
7092
  impl magnus::TryConvert for CrawlEvent {
6594
7093
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
6595
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
7094
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
7095
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
6596
7096
  // For unit enums or when passed as a string, fall back to string-based conversion.
6597
7097
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
6598
7098
  s
@@ -6624,9 +7124,10 @@ impl magnus::TryConvert for CrawlEvent {
6624
7124
 
6625
7125
  unsafe impl IntoValueFromNative for CrawlEvent {}
6626
7126
  unsafe impl TryConvertOwned for CrawlEvent {}
7127
+
6627
7128
  impl CrawlEvent {}
6628
7129
 
6629
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
7130
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
6630
7131
  #[serde(rename_all = "snake_case")]
6631
7132
  pub enum CrawlStrategyKind {
6632
7133
  Bfs,
@@ -6655,7 +7156,14 @@ impl magnus::IntoValue for CrawlStrategyKind {
6655
7156
 
6656
7157
  impl magnus::TryConvert for CrawlStrategyKind {
6657
7158
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
6658
- let s: String = magnus::TryConvert::try_convert(val)?;
7159
+ // IntoValue emits Symbols, including when this enum is a Hash key.
7160
+ // Accept that exact representation as well as String inputs; do not
7161
+ // coerce arbitrary objects through to_s.
7162
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
7163
+ symbol.name()?.into_owned()
7164
+ } else {
7165
+ magnus::TryConvert::try_convert(val)?
7166
+ };
6659
7167
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
6660
7168
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
6661
7169
  // existing consumer code written in any of those styles keep working.
@@ -6675,7 +7183,7 @@ impl magnus::TryConvert for CrawlStrategyKind {
6675
7183
  unsafe impl IntoValueFromNative for CrawlStrategyKind {}
6676
7184
  unsafe impl TryConvertOwned for CrawlStrategyKind {}
6677
7185
 
6678
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
7186
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
6679
7187
  #[serde(rename_all = "snake_case")]
6680
7188
  pub enum DocumentContentEncoding {
6681
7189
  Base64,
@@ -6698,7 +7206,14 @@ impl magnus::IntoValue for DocumentContentEncoding {
6698
7206
 
6699
7207
  impl magnus::TryConvert for DocumentContentEncoding {
6700
7208
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
6701
- let s: String = magnus::TryConvert::try_convert(val)?;
7209
+ // IntoValue emits Symbols, including when this enum is a Hash key.
7210
+ // Accept that exact representation as well as String inputs; do not
7211
+ // coerce arbitrary objects through to_s.
7212
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
7213
+ symbol.name()?.into_owned()
7214
+ } else {
7215
+ magnus::TryConvert::try_convert(val)?
7216
+ };
6702
7217
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
6703
7218
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
6704
7219
  // existing consumer code written in any of those styles keep working.
@@ -6715,7 +7230,7 @@ impl magnus::TryConvert for DocumentContentEncoding {
6715
7230
  unsafe impl IntoValueFromNative for DocumentContentEncoding {}
6716
7231
  unsafe impl TryConvertOwned for DocumentContentEncoding {}
6717
7232
 
6718
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
7233
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
6719
7234
  #[serde(rename_all = "snake_case")]
6720
7235
  pub enum FeedType {
6721
7236
  Rss,
@@ -6742,7 +7257,14 @@ impl magnus::IntoValue for FeedType {
6742
7257
 
6743
7258
  impl magnus::TryConvert for FeedType {
6744
7259
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
6745
- let s: String = magnus::TryConvert::try_convert(val)?;
7260
+ // IntoValue emits Symbols, including when this enum is a Hash key.
7261
+ // Accept that exact representation as well as String inputs; do not
7262
+ // coerce arbitrary objects through to_s.
7263
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
7264
+ symbol.name()?.into_owned()
7265
+ } else {
7266
+ magnus::TryConvert::try_convert(val)?
7267
+ };
6746
7268
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
6747
7269
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
6748
7270
  // existing consumer code written in any of those styles keep working.
@@ -6770,14 +7292,6 @@ pub enum HostMatcher {
6770
7292
  Cidr { value: String },
6771
7293
  }
6772
7294
 
6773
- impl Default for HostMatcher {
6774
- fn default() -> Self {
6775
- Self::Exact {
6776
- value: Default::default(),
6777
- }
6778
- }
6779
- }
6780
-
6781
7295
  impl magnus::IntoValue for HostMatcher {
6782
7296
  fn into_value_with(self, handle: &Ruby) -> magnus::Value {
6783
7297
  match serde_json::to_value(&self) {
@@ -6789,7 +7303,8 @@ impl magnus::IntoValue for HostMatcher {
6789
7303
 
6790
7304
  impl magnus::TryConvert for HostMatcher {
6791
7305
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
6792
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
7306
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
7307
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
6793
7308
  // For unit enums or when passed as a string, fall back to string-based conversion.
6794
7309
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
6795
7310
  s
@@ -6821,9 +7336,10 @@ impl magnus::TryConvert for HostMatcher {
6821
7336
 
6822
7337
  unsafe impl IntoValueFromNative for HostMatcher {}
6823
7338
  unsafe impl TryConvertOwned for HostMatcher {}
7339
+
6824
7340
  impl HostMatcher {}
6825
7341
 
6826
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
7342
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
6827
7343
  #[serde(rename_all = "snake_case")]
6828
7344
  pub enum ImageSource {
6829
7345
  Img,
@@ -6854,7 +7370,14 @@ impl magnus::IntoValue for ImageSource {
6854
7370
 
6855
7371
  impl magnus::TryConvert for ImageSource {
6856
7372
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
6857
- let s: String = magnus::TryConvert::try_convert(val)?;
7373
+ // IntoValue emits Symbols, including when this enum is a Hash key.
7374
+ // Accept that exact representation as well as String inputs; do not
7375
+ // coerce arbitrary objects through to_s.
7376
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
7377
+ symbol.name()?.into_owned()
7378
+ } else {
7379
+ magnus::TryConvert::try_convert(val)?
7380
+ };
6858
7381
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
6859
7382
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
6860
7383
  // existing consumer code written in any of those styles keep working.
@@ -6874,7 +7397,7 @@ impl magnus::TryConvert for ImageSource {
6874
7397
  unsafe impl IntoValueFromNative for ImageSource {}
6875
7398
  unsafe impl TryConvertOwned for ImageSource {}
6876
7399
 
6877
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
7400
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
6878
7401
  #[serde(rename_all = "snake_case")]
6879
7402
  pub enum LinkType {
6880
7403
  Internal,
@@ -6903,7 +7426,14 @@ impl magnus::IntoValue for LinkType {
6903
7426
 
6904
7427
  impl magnus::TryConvert for LinkType {
6905
7428
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
6906
- let s: String = magnus::TryConvert::try_convert(val)?;
7429
+ // IntoValue emits Symbols, including when this enum is a Hash key.
7430
+ // Accept that exact representation as well as String inputs; do not
7431
+ // coerce arbitrary objects through to_s.
7432
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
7433
+ symbol.name()?.into_owned()
7434
+ } else {
7435
+ magnus::TryConvert::try_convert(val)?
7436
+ };
6907
7437
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
6908
7438
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
6909
7439
  // existing consumer code written in any of those styles keep working.
@@ -6923,7 +7453,7 @@ impl magnus::TryConvert for LinkType {
6923
7453
  unsafe impl IntoValueFromNative for LinkType {}
6924
7454
  unsafe impl TryConvertOwned for LinkType {}
6925
7455
 
6926
- #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
7456
+ #[derive(Clone, serde::Serialize, serde::Deserialize)]
6927
7457
  #[serde(tag = "type")]
6928
7458
  #[serde(rename_all = "camelCase")]
6929
7459
  pub enum PageAction {
@@ -6974,7 +7504,8 @@ impl magnus::IntoValue for PageAction {
6974
7504
 
6975
7505
  impl magnus::TryConvert for PageAction {
6976
7506
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
6977
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
7507
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
7508
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
6978
7509
  // For unit enums or when passed as a string, fall back to string-based conversion.
6979
7510
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
6980
7511
  s
@@ -7006,9 +7537,15 @@ impl magnus::TryConvert for PageAction {
7006
7537
 
7007
7538
  unsafe impl IntoValueFromNative for PageAction {}
7008
7539
  unsafe impl TryConvertOwned for PageAction {}
7540
+
7009
7541
  impl PageAction {}
7542
+ impl std::fmt::Debug for PageAction {
7543
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7544
+ f.write_str("PageAction(<redacted>)")
7545
+ }
7546
+ }
7010
7547
 
7011
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
7548
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
7012
7549
  #[serde(rename_all = "camelCase")]
7013
7550
  pub enum ScrollDirection {
7014
7551
  Up,
@@ -7033,7 +7570,14 @@ impl magnus::IntoValue for ScrollDirection {
7033
7570
 
7034
7571
  impl magnus::TryConvert for ScrollDirection {
7035
7572
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
7036
- let s: String = magnus::TryConvert::try_convert(val)?;
7573
+ // IntoValue emits Symbols, including when this enum is a Hash key.
7574
+ // Accept that exact representation as well as String inputs; do not
7575
+ // coerce arbitrary objects through to_s.
7576
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
7577
+ symbol.name()?.into_owned()
7578
+ } else {
7579
+ magnus::TryConvert::try_convert(val)?
7580
+ };
7037
7581
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
7038
7582
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
7039
7583
  // existing consumer code written in any of those styles keep working.
@@ -7445,6 +7989,8 @@ impl From<BrowserConfig> for crawlberg::BrowserConfig {
7445
7989
  backend: val.backend.into(),
7446
7990
  endpoint: val.endpoint,
7447
7991
  timeout: std::time::Duration::from_millis(val.timeout),
7992
+ overall_timeout: std::time::Duration::from_millis(val.overall_timeout),
7993
+ shutdown_timeout: std::time::Duration::from_millis(val.shutdown_timeout),
7448
7994
  wait: val.wait.into(),
7449
7995
  wait_selector: val.wait_selector,
7450
7996
  extra_wait: val.extra_wait.map(std::time::Duration::from_millis),
@@ -7454,6 +8000,8 @@ impl From<BrowserConfig> for crawlberg::BrowserConfig {
7454
8000
  robots_user_agent: val.robots_user_agent,
7455
8001
  capture_network_events: val.capture_network_events,
7456
8002
  session_affinity: val.session_affinity,
8003
+ chrome_path: val.chrome_path.map(Into::into),
8004
+ chrome_args: val.chrome_args.into_iter().collect(),
7457
8005
  ..Default::default()
7458
8006
  }
7459
8007
  }
@@ -7467,6 +8015,8 @@ impl From<crawlberg::BrowserConfig> for BrowserConfig {
7467
8015
  backend: val.backend.into(),
7468
8016
  endpoint: val.endpoint.map(|v| v.to_string()),
7469
8017
  timeout: val.timeout.as_millis() as u64,
8018
+ overall_timeout: val.overall_timeout.as_millis() as u64,
8019
+ shutdown_timeout: val.shutdown_timeout.as_millis() as u64,
7470
8020
  wait: val.wait.into(),
7471
8021
  wait_selector: val.wait_selector.map(|v| v.to_string()),
7472
8022
  extra_wait: val.extra_wait.map(|d| d.as_millis() as u64),
@@ -7476,6 +8026,8 @@ impl From<crawlberg::BrowserConfig> for BrowserConfig {
7476
8026
  robots_user_agent: val.robots_user_agent.map(|v| v.to_string()),
7477
8027
  capture_network_events: val.capture_network_events,
7478
8028
  session_affinity: val.session_affinity,
8029
+ chrome_path: val.chrome_path.map(|p| p.to_string_lossy().to_string()),
8030
+ chrome_args: val.chrome_args.into_iter().collect(),
7479
8031
  }
7480
8032
  }
7481
8033
  }
@@ -7564,6 +8116,7 @@ impl From<ContentConfig> for crawlberg::ContentConfig {
7564
8116
  wrap: val.wrap,
7565
8117
  wrap_width: val.wrap_width,
7566
8118
  include_document_structure: val.include_document_structure,
8119
+ extract_metadata: val.extract_metadata,
7567
8120
  ..Default::default()
7568
8121
  }
7569
8122
  }
@@ -7584,6 +8137,7 @@ impl From<crawlberg::ContentConfig> for ContentConfig {
7584
8137
  wrap: val.wrap,
7585
8138
  wrap_width: val.wrap_width,
7586
8139
  include_document_structure: val.include_document_structure,
8140
+ extract_metadata: val.extract_metadata,
7587
8141
  }
7588
8142
  }
7589
8143
  }
@@ -7633,6 +8187,11 @@ impl From<CrawlConfig> for crawlberg::CrawlConfig {
7633
8187
  allow_subdomains: val.allow_subdomains,
7634
8188
  include_paths: val.include_paths.into_iter().collect(),
7635
8189
  exclude_paths: val.exclude_paths.into_iter().collect(),
8190
+ path_patterns_match_query: val.path_patterns_match_query,
8191
+ path_patterns_match_url: val.path_patterns_match_url,
8192
+ dedup_include_query: val.dedup_include_query,
8193
+ strip_tracking_params: val.strip_tracking_params,
8194
+ tracking_params: val.tracking_params.into_iter().collect(),
7636
8195
  custom_headers: val
7637
8196
  .custom_headers
7638
8197
  .into_iter()
@@ -7643,6 +8202,9 @@ impl From<CrawlConfig> for crawlberg::CrawlConfig {
7643
8202
  max_redirects: val.max_redirects,
7644
8203
  retry_count: val.retry_count,
7645
8204
  retry_codes: val.retry_codes.into_iter().collect(),
8205
+ retry_initial_delay_ms: val.retry_initial_delay_ms,
8206
+ retry_max_delay_ms: val.retry_max_delay_ms,
8207
+ rate_limit_jitter_ratio: val.rate_limit_jitter_ratio,
7646
8208
  cookies_enabled: val.cookies_enabled,
7647
8209
  auth: val.auth.map(Into::into),
7648
8210
  max_body_size: val.max_body_size,
@@ -7693,6 +8255,11 @@ impl From<crawlberg::CrawlConfig> for CrawlConfig {
7693
8255
  allow_subdomains: val.allow_subdomains,
7694
8256
  include_paths: val.include_paths.into_iter().collect(),
7695
8257
  exclude_paths: val.exclude_paths.into_iter().collect(),
8258
+ path_patterns_match_query: val.path_patterns_match_query,
8259
+ path_patterns_match_url: val.path_patterns_match_url,
8260
+ dedup_include_query: val.dedup_include_query,
8261
+ strip_tracking_params: val.strip_tracking_params,
8262
+ tracking_params: val.tracking_params.into_iter().collect(),
7696
8263
  custom_headers: val
7697
8264
  .custom_headers
7698
8265
  .into_iter()
@@ -7703,6 +8270,9 @@ impl From<crawlberg::CrawlConfig> for CrawlConfig {
7703
8270
  max_redirects: val.max_redirects,
7704
8271
  retry_count: val.retry_count,
7705
8272
  retry_codes: val.retry_codes.into_iter().collect(),
8273
+ retry_initial_delay_ms: val.retry_initial_delay_ms,
8274
+ retry_max_delay_ms: val.retry_max_delay_ms,
8275
+ rate_limit_jitter_ratio: val.rate_limit_jitter_ratio,
7706
8276
  cookies_enabled: val.cookies_enabled,
7707
8277
  auth: val.auth.map(Into::into),
7708
8278
  max_body_size: val.max_body_size,
@@ -7759,6 +8329,11 @@ impl From<CrawlPageResult> for crawlberg::CrawlPageResult {
7759
8329
  extraction_meta: val.extraction_meta.map(Into::into),
7760
8330
  downloaded_document: val.downloaded_document.map(Into::into),
7761
8331
  browser_used: val.browser_used,
8332
+ final_url: val.final_url,
8333
+ redirect_count: val.redirect_count,
8334
+ noindex_detected: val.noindex_detected,
8335
+ nofollow_detected: val.nofollow_detected,
8336
+ ssrf_refused_urls: val.ssrf_refused_urls.into_iter().collect(),
7762
8337
  ..Default::default()
7763
8338
  }
7764
8339
  }
@@ -7789,6 +8364,11 @@ impl From<crawlberg::CrawlPageResult> for CrawlPageResult {
7789
8364
  extraction_meta: val.extraction_meta.map(Into::into),
7790
8365
  downloaded_document: val.downloaded_document.map(Into::into),
7791
8366
  browser_used: val.browser_used,
8367
+ final_url: val.final_url.to_string(),
8368
+ redirect_count: val.redirect_count,
8369
+ noindex_detected: val.noindex_detected,
8370
+ nofollow_detected: val.nofollow_detected,
8371
+ ssrf_refused_urls: val.ssrf_refused_urls.into_iter().collect(),
7792
8372
  }
7793
8373
  }
7794
8374
  }
@@ -8066,6 +8646,7 @@ impl From<InteractionResult> for crawlberg::InteractionResult {
8066
8646
  final_html: val.final_html,
8067
8647
  final_url: val.final_url,
8068
8648
  screenshot_base64: val.screenshot_base64,
8649
+ ssrf_refused_urls: val.ssrf_refused_urls.into_iter().collect(),
8069
8650
  ..Default::default()
8070
8651
  }
8071
8652
  }
@@ -8079,6 +8660,7 @@ impl From<crawlberg::InteractionResult> for InteractionResult {
8079
8660
  final_html: val.final_html.to_string(),
8080
8661
  final_url: val.final_url.to_string(),
8081
8662
  screenshot_base64: val.screenshot_base64.map(|v| v.to_string()),
8663
+ ssrf_refused_urls: val.ssrf_refused_urls.into_iter().collect(),
8082
8664
  }
8083
8665
  }
8084
8666
  }
@@ -8383,6 +8965,7 @@ impl From<ScrapeResult> for crawlberg::ScrapeResult {
8383
8965
  screenshot_base64: val.screenshot_base64,
8384
8966
  downloaded_document: val.downloaded_document.map(Into::into),
8385
8967
  browser: val.browser.map(Into::into),
8968
+ ssrf_refused_urls: val.ssrf_refused_urls.into_iter().collect(),
8386
8969
  ..Default::default()
8387
8970
  }
8388
8971
  }
@@ -8421,6 +9004,7 @@ impl From<crawlberg::ScrapeResult> for ScrapeResult {
8421
9004
  screenshot_base64: val.screenshot_base64.map(|v| v.to_string()),
8422
9005
  downloaded_document: val.downloaded_document.map(Into::into),
8423
9006
  browser: val.browser.map(Into::into),
9007
+ ssrf_refused_urls: val.ssrf_refused_urls.into_iter().collect(),
8424
9008
  }
8425
9009
  }
8426
9010
  }
@@ -8457,6 +9041,7 @@ impl From<SsrfPolicy> for crawlberg::SsrfPolicy {
8457
9041
  Self {
8458
9042
  deny_private: val.deny_private,
8459
9043
  allowlist: val.allowlist.into_iter().map(Into::into).collect(),
9044
+ denylist: val.denylist.into_iter().map(Into::into).collect(),
8460
9045
  max_redirects: val.max_redirects,
8461
9046
  scheme_allowlist: val.scheme_allowlist.into_iter().collect(),
8462
9047
  ..Default::default()
@@ -8470,6 +9055,7 @@ impl From<crawlberg::SsrfPolicy> for SsrfPolicy {
8470
9055
  Self {
8471
9056
  deny_private: val.deny_private,
8472
9057
  allowlist: val.allowlist.into_iter().map(Into::into).collect(),
9058
+ denylist: val.denylist.into_iter().map(Into::into).collect(),
8473
9059
  max_redirects: val.max_redirects,
8474
9060
  scheme_allowlist: val.scheme_allowlist.into_iter().collect(),
8475
9061
  }
@@ -8619,7 +9205,7 @@ impl From<crawlberg::CrawlEvent> for CrawlEvent {
8619
9205
  fn from(val: crawlberg::CrawlEvent) -> Self {
8620
9206
  match val {
8621
9207
  crawlberg::CrawlEvent::Page { result } => Self::Page {
8622
- result: (*result).into(),
9208
+ result: Box::new((*result).into()),
8623
9209
  },
8624
9210
  crawlberg::CrawlEvent::Error { url, error } => Self::Error {
8625
9211
  url: url.to_string(),
@@ -9109,6 +9695,10 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
9109
9695
 
9110
9696
  class.define_method("timeout", method!(BrowserConfig::timeout, 0))?;
9111
9697
 
9698
+ class.define_method("overall_timeout", method!(BrowserConfig::overall_timeout, 0))?;
9699
+
9700
+ class.define_method("shutdown_timeout", method!(BrowserConfig::shutdown_timeout, 0))?;
9701
+
9112
9702
  class.define_method("wait", method!(BrowserConfig::wait, 0))?;
9113
9703
 
9114
9704
  class.define_method("wait_selector", method!(BrowserConfig::wait_selector, 0))?;
@@ -9130,6 +9720,10 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
9130
9720
 
9131
9721
  class.define_method("session_affinity", method!(BrowserConfig::session_affinity, 0))?;
9132
9722
 
9723
+ class.define_method("chrome_path", method!(BrowserConfig::chrome_path, 0))?;
9724
+
9725
+ class.define_method("chrome_args", method!(BrowserConfig::chrome_args, 0))?;
9726
+
9133
9727
  let class = module.define_class("BrowserExtras", ruby.class_object())?;
9134
9728
 
9135
9729
  class.define_singleton_method("new", function!(BrowserExtras::new, -1))?;
@@ -9191,6 +9785,8 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
9191
9785
  method!(ContentConfig::include_document_structure, 0),
9192
9786
  )?;
9193
9787
 
9788
+ class.define_method("extract_metadata", method!(ContentConfig::extract_metadata, 0))?;
9789
+
9194
9790
  let class = module.define_class("CookieInfo", ruby.class_object())?;
9195
9791
 
9196
9792
  class.define_singleton_method("new", function!(CookieInfo::new, -1))?;
@@ -9237,6 +9833,22 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
9237
9833
 
9238
9834
  class.define_method("exclude_paths", method!(CrawlConfig::exclude_paths, 0))?;
9239
9835
 
9836
+ class.define_method(
9837
+ "path_patterns_match_query",
9838
+ method!(CrawlConfig::path_patterns_match_query, 0),
9839
+ )?;
9840
+
9841
+ class.define_method(
9842
+ "path_patterns_match_url",
9843
+ method!(CrawlConfig::path_patterns_match_url, 0),
9844
+ )?;
9845
+
9846
+ class.define_method("dedup_include_query", method!(CrawlConfig::dedup_include_query, 0))?;
9847
+
9848
+ class.define_method("strip_tracking_params", method!(CrawlConfig::strip_tracking_params, 0))?;
9849
+
9850
+ class.define_method("tracking_params", method!(CrawlConfig::tracking_params, 0))?;
9851
+
9240
9852
  class.define_method("custom_headers", method!(CrawlConfig::custom_headers, 0))?;
9241
9853
 
9242
9854
  class.define_method("request_timeout", method!(CrawlConfig::request_timeout, 0))?;
@@ -9249,6 +9861,18 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
9249
9861
 
9250
9862
  class.define_method("retry_codes", method!(CrawlConfig::retry_codes, 0))?;
9251
9863
 
9864
+ class.define_method(
9865
+ "retry_initial_delay_ms",
9866
+ method!(CrawlConfig::retry_initial_delay_ms, 0),
9867
+ )?;
9868
+
9869
+ class.define_method("retry_max_delay_ms", method!(CrawlConfig::retry_max_delay_ms, 0))?;
9870
+
9871
+ class.define_method(
9872
+ "rate_limit_jitter_ratio",
9873
+ method!(CrawlConfig::rate_limit_jitter_ratio, 0),
9874
+ )?;
9875
+
9252
9876
  class.define_method("cookies_enabled", method!(CrawlConfig::cookies_enabled, 0))?;
9253
9877
 
9254
9878
  class.define_method("auth", method!(CrawlConfig::auth, 0))?;
@@ -9360,6 +9984,16 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
9360
9984
 
9361
9985
  class.define_method("browser_used", method!(CrawlPageResult::browser_used, 0))?;
9362
9986
 
9987
+ class.define_method("final_url", method!(CrawlPageResult::final_url, 0))?;
9988
+
9989
+ class.define_method("redirect_count", method!(CrawlPageResult::redirect_count, 0))?;
9990
+
9991
+ class.define_method("noindex_detected", method!(CrawlPageResult::noindex_detected, 0))?;
9992
+
9993
+ class.define_method("nofollow_detected", method!(CrawlPageResult::nofollow_detected, 0))?;
9994
+
9995
+ class.define_method("ssrf_refused_urls", method!(CrawlPageResult::ssrf_refused_urls, 0))?;
9996
+
9363
9997
  let class = module.define_class("CrawlResult", ruby.class_object())?;
9364
9998
 
9365
9999
  class.define_singleton_method("new", function!(CrawlResult::new, -1))?;
@@ -9509,6 +10143,8 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
9509
10143
 
9510
10144
  class.define_method("screenshot_base64", method!(InteractionResult::screenshot_base64, 0))?;
9511
10145
 
10146
+ class.define_method("ssrf_refused_urls", method!(InteractionResult::ssrf_refused_urls, 0))?;
10147
+
9512
10148
  let class = module.define_class("JsonLdEntry", ruby.class_object())?;
9513
10149
 
9514
10150
  class.define_singleton_method("new", function!(JsonLdEntry::new, -1))?;
@@ -9737,6 +10373,8 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
9737
10373
 
9738
10374
  class.define_method("browser", method!(ScrapeResult::browser, 0))?;
9739
10375
 
10376
+ class.define_method("ssrf_refused_urls", method!(ScrapeResult::ssrf_refused_urls, 0))?;
10377
+
9740
10378
  let class = module.define_class("SitemapUrl", ruby.class_object())?;
9741
10379
 
9742
10380
  class.define_singleton_method("new", function!(SitemapUrl::new, -1))?;
@@ -9757,6 +10395,8 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
9757
10395
 
9758
10396
  class.define_method("allowlist", method!(SsrfPolicy::allowlist, 0))?;
9759
10397
 
10398
+ class.define_method("denylist", method!(SsrfPolicy::denylist, 0))?;
10399
+
9760
10400
  class.define_method("max_redirects", method!(SsrfPolicy::max_redirects, 0))?;
9761
10401
 
9762
10402
  class.define_method("scheme_allowlist", method!(SsrfPolicy::scheme_allowlist, 0))?;