1use serde::{Deserialize, Serialize};
2
3pub use crate::tls::TlsConfig;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum ContentStatus {
14 Ok,
16 Empty,
18 NeedsJs,
21 TooComplex,
24}
25
26impl ContentStatus {
27 pub fn is_failure(self) -> bool {
29 !matches!(self, ContentStatus::Ok)
30 }
31
32 pub fn note(self) -> Option<&'static str> {
34 match self {
35 ContentStatus::Ok => None,
36 ContentStatus::Empty => Some("the page parsed but contains no text"),
37 ContentStatus::NeedsJs => Some(
38 "no text content: the page renders its body with JavaScript, \
39 which webtools does not execute",
40 ),
41 ContentStatus::TooComplex => {
42 Some("the document is too deeply nested to parse safely and was refused")
43 }
44 }
45 }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct FetchResult {
51 pub title: String,
52 pub final_url: String,
54 pub content: String,
55 pub content_type: ContentType,
56 pub media: String,
59 pub token_estimate: usize,
60 pub status: ContentStatus,
62 pub references: Vec<UrlReference>,
66 #[serde(default)]
67 pub metadata: Metadata,
68 pub source: String,
70}
71
72#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
74pub struct Metadata {
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub description: Option<String>,
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub author: Option<String>,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 pub published: Option<String>,
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub lang: Option<String>,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub site_name: Option<String>,
85 #[serde(skip_serializing_if = "Option::is_none")]
88 pub charset: Option<String>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
93pub struct UrlReference {
94 pub index: usize,
95 pub url: String,
96 pub text: String,
98}
99
100impl crate::refs::Referable for UrlReference {
101 fn index(&self) -> usize {
102 self.index
103 }
104 fn url(&self) -> &str {
105 &self.url
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
110#[serde(rename_all = "lowercase")]
111pub enum ContentType {
112 Text,
113 Markdown,
114 Structured,
115}
116
117impl ContentType {
118 pub fn parse(s: &str) -> Self {
119 match s.to_ascii_lowercase().as_str() {
120 "markdown" | "md" => ContentType::Markdown,
121 "structured" | "json" => ContentType::Structured,
122 _ => ContentType::Text,
123 }
124 }
125}
126
127#[derive(Debug, Clone, Deserialize)]
128pub struct FetchOptions {
129 pub url: String,
130 pub content_type: ContentType,
131 pub max_tokens: Option<usize>,
132 pub timeout_secs: u64,
133 #[serde(default)]
136 pub tls: TlsConfig,
137}
138
139impl Default for FetchOptions {
140 fn default() -> Self {
141 Self {
142 url: String::new(),
143 content_type: ContentType::Text,
144 max_tokens: None,
145 timeout_secs: 10,
146 tls: TlsConfig::default(),
147 }
148 }
149}