use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct ReviewSnippet {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub review_id: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum Annotation {
UrlCitation {
url: Option<String>,
title: Option<String>,
start_index: usize,
end_index: usize,
},
FileCitation {
document_uri: Option<String>,
file_name: Option<String>,
source: Option<String>,
custom_metadata: Option<serde_json::Value>,
page_number: Option<u32>,
media_id: Option<String>,
start_index: usize,
end_index: usize,
},
PlaceCitation {
place_id: Option<String>,
name: Option<String>,
url: Option<String>,
review_snippets: Vec<ReviewSnippet>,
start_index: usize,
end_index: usize,
},
Unknown {
annotation_type: String,
data: serde_json::Value,
},
}
impl Annotation {
#[must_use]
pub fn url_citation(
url: impl Into<String>,
title: Option<String>,
start_index: usize,
end_index: usize,
) -> Self {
Self::UrlCitation {
url: Some(url.into()),
title,
start_index,
end_index,
}
}
#[must_use]
pub const fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown { .. })
}
#[must_use]
pub fn unknown_annotation_type(&self) -> Option<&str> {
match self {
Self::Unknown {
annotation_type, ..
} => Some(annotation_type),
_ => None,
}
}
#[must_use]
pub fn unknown_data(&self) -> Option<&serde_json::Value> {
match self {
Self::Unknown { data, .. } => Some(data),
_ => None,
}
}
#[must_use]
pub fn start_index(&self) -> Option<usize> {
match self {
Self::UrlCitation { start_index, .. }
| Self::FileCitation { start_index, .. }
| Self::PlaceCitation { start_index, .. } => Some(*start_index),
Self::Unknown { data, .. } => data
.get("start_index")
.and_then(|v| v.as_u64())
.map(|v| v as usize),
}
}
#[must_use]
pub fn end_index(&self) -> Option<usize> {
match self {
Self::UrlCitation { end_index, .. }
| Self::FileCitation { end_index, .. }
| Self::PlaceCitation { end_index, .. } => Some(*end_index),
Self::Unknown { data, .. } => data
.get("end_index")
.and_then(|v| v.as_u64())
.map(|v| v as usize),
}
}
#[must_use]
pub fn source(&self) -> Option<&str> {
match self {
Self::UrlCitation { url, .. } => url.as_deref(),
Self::FileCitation {
document_uri,
file_name,
..
} => document_uri.as_deref().or(file_name.as_deref()),
Self::PlaceCitation { url, place_id, .. } => url.as_deref().or(place_id.as_deref()),
Self::Unknown { .. } => None,
}
}
#[must_use]
pub fn extract_span<'a>(&self, text: &'a str) -> Option<&'a str> {
let start = self.start_index()?;
let end = self.end_index()?;
let bytes = text.as_bytes();
if end <= bytes.len() && start <= end {
std::str::from_utf8(&bytes[start..end]).ok()
} else {
None
}
}
}
impl Serialize for Annotation {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeMap;
let mut map = serializer.serialize_map(None)?;
match self {
Self::UrlCitation {
url,
title,
start_index,
end_index,
} => {
map.serialize_entry("type", "url_citation")?;
if let Some(u) = url {
map.serialize_entry("url", u)?;
}
if let Some(t) = title {
map.serialize_entry("title", t)?;
}
map.serialize_entry("start_index", start_index)?;
map.serialize_entry("end_index", end_index)?;
}
Self::FileCitation {
document_uri,
file_name,
source,
custom_metadata,
page_number,
media_id,
start_index,
end_index,
} => {
map.serialize_entry("type", "file_citation")?;
if let Some(d) = document_uri {
map.serialize_entry("document_uri", d)?;
}
if let Some(f) = file_name {
map.serialize_entry("file_name", f)?;
}
if let Some(s) = source {
map.serialize_entry("source", s)?;
}
if let Some(c) = custom_metadata {
map.serialize_entry("custom_metadata", c)?;
}
if let Some(p) = page_number {
map.serialize_entry("page_number", p)?;
}
if let Some(m) = media_id {
map.serialize_entry("media_id", m)?;
}
map.serialize_entry("start_index", start_index)?;
map.serialize_entry("end_index", end_index)?;
}
Self::PlaceCitation {
place_id,
name,
url,
review_snippets,
start_index,
end_index,
} => {
map.serialize_entry("type", "place_citation")?;
if let Some(p) = place_id {
map.serialize_entry("place_id", p)?;
}
if let Some(n) = name {
map.serialize_entry("name", n)?;
}
if let Some(u) = url {
map.serialize_entry("url", u)?;
}
if !review_snippets.is_empty() {
map.serialize_entry("review_snippets", review_snippets)?;
}
map.serialize_entry("start_index", start_index)?;
map.serialize_entry("end_index", end_index)?;
}
Self::Unknown {
annotation_type,
data,
} => {
map.serialize_entry("type", annotation_type)?;
match data {
serde_json::Value::Object(obj) => {
for (key, value) in obj {
if key != "type" {
map.serialize_entry(key, value)?;
}
}
}
other if !other.is_null() => {
map.serialize_entry("data", other)?;
}
_ => {}
}
}
}
map.end()
}
}
impl<'de> Deserialize<'de> for Annotation {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
#[derive(Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
#[allow(clippy::enum_variant_names)]
enum KnownAnnotation {
UrlCitation {
#[serde(default)]
url: Option<String>,
#[serde(default)]
title: Option<String>,
#[serde(default)]
start_index: usize,
#[serde(default)]
end_index: usize,
},
FileCitation {
#[serde(default)]
document_uri: Option<String>,
#[serde(default)]
file_name: Option<String>,
#[serde(default)]
source: Option<String>,
#[serde(default)]
custom_metadata: Option<serde_json::Value>,
#[serde(default)]
page_number: Option<u32>,
#[serde(default)]
media_id: Option<String>,
#[serde(default)]
start_index: usize,
#[serde(default)]
end_index: usize,
},
PlaceCitation {
#[serde(default)]
place_id: Option<String>,
#[serde(default)]
name: Option<String>,
#[serde(default)]
url: Option<String>,
#[serde(default)]
review_snippets: Vec<ReviewSnippet>,
#[serde(default)]
start_index: usize,
#[serde(default)]
end_index: usize,
},
}
match serde_json::from_value::<KnownAnnotation>(value.clone()) {
Ok(known) => Ok(match known {
KnownAnnotation::UrlCitation {
url,
title,
start_index,
end_index,
} => Annotation::UrlCitation {
url,
title,
start_index,
end_index,
},
KnownAnnotation::FileCitation {
document_uri,
file_name,
source,
custom_metadata,
page_number,
media_id,
start_index,
end_index,
} => Annotation::FileCitation {
document_uri,
file_name,
source,
custom_metadata,
page_number,
media_id,
start_index,
end_index,
},
KnownAnnotation::PlaceCitation {
place_id,
name,
url,
review_snippets,
start_index,
end_index,
} => Annotation::PlaceCitation {
place_id,
name,
url,
review_snippets,
start_index,
end_index,
},
}),
Err(parse_error) => {
let annotation_type = value
.get("type")
.and_then(|v| v.as_str())
.unwrap_or("<missing type>")
.to_string();
tracing::warn!(
"Encountered unknown Annotation type '{}'. Parse error: {}. \
The annotation will be preserved in the Unknown variant.",
annotation_type,
parse_error
);
Ok(Annotation::Unknown {
annotation_type,
data: value,
})
}
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct GoogleSearchResultItem {
#[serde(skip_serializing_if = "String::is_empty")]
pub title: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub rendered_content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub search_suggestions: Option<String>,
}
impl GoogleSearchResultItem {
#[must_use]
pub fn new(title: impl Into<String>, url: impl Into<String>) -> Self {
Self {
title: title.into(),
url: url.into(),
rendered_content: None,
search_suggestions: None,
}
}
#[must_use]
pub fn has_rendered_content(&self) -> bool {
self.rendered_content.is_some()
}
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct Place {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub formatted_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub place_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lat: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lng: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub types: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rating: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_ratings_total: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub website: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub phone_number: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub review_snippets: Option<Vec<ReviewSnippet>>,
#[serde(flatten)]
pub extra: serde_json::Map<String, serde_json::Value>,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct GoogleMapsResultItem {
#[serde(skip_serializing_if = "Option::is_none")]
pub places: Option<Vec<Place>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub widget_context_token: Option<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct UrlContextResultItem {
pub url: String,
pub status: String,
}
impl UrlContextResultItem {
#[must_use]
pub fn new(url: impl Into<String>, status: impl Into<String>) -> Self {
Self {
url: url.into(),
status: status.into(),
}
}
#[must_use]
pub fn is_success(&self) -> bool {
self.status == "success"
}
#[must_use]
pub fn is_error(&self) -> bool {
self.status == "error"
}
#[must_use]
pub fn is_unsafe(&self) -> bool {
self.status == "unsafe"
}
#[must_use]
pub fn is_paywall(&self) -> bool {
self.status == "paywall"
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct FileSearchResultItem {
pub title: String,
pub text: String,
#[serde(rename = "file_search_store")]
pub store: String,
}
impl FileSearchResultItem {
#[must_use]
pub fn new(
title: impl Into<String>,
text: impl Into<String>,
store: impl Into<String>,
) -> Self {
Self {
title: title.into(),
text: text.into(),
store: store.into(),
}
}
#[must_use]
pub fn has_text(&self) -> bool {
!self.text.is_empty()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum CodeExecutionLanguage {
#[default]
Python,
Unknown {
language_type: String,
data: serde_json::Value,
},
}
impl CodeExecutionLanguage {
#[must_use]
pub const fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown { .. })
}
#[must_use]
pub fn unknown_language_type(&self) -> Option<&str> {
match self {
Self::Unknown { language_type, .. } => Some(language_type),
_ => None,
}
}
#[must_use]
pub fn unknown_data(&self) -> Option<&serde_json::Value> {
match self {
Self::Unknown { data, .. } => Some(data),
_ => None,
}
}
}
impl Serialize for CodeExecutionLanguage {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Self::Python => serializer.serialize_str("python"),
Self::Unknown { language_type, .. } => serializer.serialize_str(language_type),
}
}
}
impl<'de> Deserialize<'de> for CodeExecutionLanguage {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
match value.as_str() {
Some("python") | Some("PYTHON") => Ok(Self::Python),
Some(other) => {
tracing::warn!(
"Encountered unknown CodeExecutionLanguage '{}'. \
This may indicate a new API feature. \
The language will be preserved in the Unknown variant.",
other
);
Ok(Self::Unknown {
language_type: other.to_string(),
data: value,
})
}
None => {
let language_type = format!("<non-string: {}>", value);
tracing::warn!(
"CodeExecutionLanguage received non-string value: {}. \
Preserving in Unknown variant.",
value
);
Ok(Self::Unknown {
language_type,
data: value,
})
}
}
}
}
impl fmt::Display for CodeExecutionLanguage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Python => write!(f, "python"),
Self::Unknown { language_type, .. } => write!(f, "{}", language_type),
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum Resolution {
Low,
#[default]
Medium,
High,
UltraHigh,
Unknown {
resolution_type: String,
data: serde_json::Value,
},
}
impl Resolution {
#[must_use]
pub const fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown { .. })
}
#[must_use]
pub fn unknown_resolution_type(&self) -> Option<&str> {
match self {
Self::Unknown {
resolution_type, ..
} => Some(resolution_type),
_ => None,
}
}
#[must_use]
pub fn unknown_data(&self) -> Option<&serde_json::Value> {
match self {
Self::Unknown { data, .. } => Some(data),
_ => None,
}
}
}
impl Serialize for Resolution {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Self::Low => serializer.serialize_str("low"),
Self::Medium => serializer.serialize_str("medium"),
Self::High => serializer.serialize_str("high"),
Self::UltraHigh => serializer.serialize_str("ultra_high"),
Self::Unknown {
resolution_type, ..
} => serializer.serialize_str(resolution_type),
}
}
}
impl<'de> Deserialize<'de> for Resolution {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
match value.as_str() {
Some("low") => Ok(Self::Low),
Some("medium") => Ok(Self::Medium),
Some("high") => Ok(Self::High),
Some("ultra_high") => Ok(Self::UltraHigh),
Some(other) => {
tracing::warn!(
"Encountered unknown Resolution '{}'. \
This may indicate a new API feature. \
The resolution will be preserved in the Unknown variant.",
other
);
Ok(Self::Unknown {
resolution_type: other.to_string(),
data: value,
})
}
None => {
let resolution_type = format!("<non-string: {}>", value);
tracing::warn!(
"Resolution received non-string value: {}. \
Preserving in Unknown variant.",
value
);
Ok(Self::Unknown {
resolution_type,
data: value,
})
}
}
}
}
impl fmt::Display for Resolution {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Low => write!(f, "low"),
Self::Medium => write!(f, "medium"),
Self::High => write!(f, "high"),
Self::UltraHigh => write!(f, "ultra_high"),
Self::Unknown {
resolution_type, ..
} => write!(f, "{}", resolution_type),
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum Content {
Text {
text: Option<String>,
annotations: Option<Vec<Annotation>>,
},
Image {
data: Option<String>,
uri: Option<String>,
mime_type: Option<String>,
resolution: Option<Resolution>,
},
Audio {
data: Option<String>,
uri: Option<String>,
mime_type: Option<String>,
sample_rate: Option<u32>,
channels: Option<u32>,
},
Video {
data: Option<String>,
uri: Option<String>,
mime_type: Option<String>,
resolution: Option<Resolution>,
},
Document {
data: Option<String>,
uri: Option<String>,
mime_type: Option<String>,
},
Unknown {
content_type: String,
data: serde_json::Value,
},
}
impl Serialize for Content {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeMap;
match self {
Self::Text { text, annotations } => {
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("type", "text")?;
if let Some(t) = text {
map.serialize_entry("text", t)?;
}
if let Some(annots) = annotations
&& !annots.is_empty()
{
map.serialize_entry("annotations", annots)?;
}
map.end()
}
Self::Image {
data,
uri,
mime_type,
resolution,
} => {
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("type", "image")?;
if let Some(d) = data {
map.serialize_entry("data", d)?;
}
if let Some(u) = uri {
map.serialize_entry("uri", u)?;
}
if let Some(m) = mime_type {
map.serialize_entry("mime_type", m)?;
}
if let Some(r) = resolution {
map.serialize_entry("resolution", r)?;
}
map.end()
}
Self::Audio {
data,
uri,
mime_type,
sample_rate,
channels,
} => {
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("type", "audio")?;
if let Some(d) = data {
map.serialize_entry("data", d)?;
}
if let Some(u) = uri {
map.serialize_entry("uri", u)?;
}
if let Some(m) = mime_type {
map.serialize_entry("mime_type", m)?;
}
if let Some(sr) = sample_rate {
map.serialize_entry("sample_rate", sr)?;
}
if let Some(c) = channels {
map.serialize_entry("channels", c)?;
}
map.end()
}
Self::Video {
data,
uri,
mime_type,
resolution,
} => {
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("type", "video")?;
if let Some(d) = data {
map.serialize_entry("data", d)?;
}
if let Some(u) = uri {
map.serialize_entry("uri", u)?;
}
if let Some(m) = mime_type {
map.serialize_entry("mime_type", m)?;
}
if let Some(r) = resolution {
map.serialize_entry("resolution", r)?;
}
map.end()
}
Self::Document {
data,
uri,
mime_type,
} => {
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("type", "document")?;
if let Some(d) = data {
map.serialize_entry("data", d)?;
}
if let Some(u) = uri {
map.serialize_entry("uri", u)?;
}
if let Some(m) = mime_type {
map.serialize_entry("mime_type", m)?;
}
map.end()
}
Self::Unknown { content_type, data } => {
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("type", content_type)?;
match data {
serde_json::Value::Object(obj) => {
for (key, value) in obj {
if key != "type" {
map.serialize_entry(key, value)?;
}
}
}
other if !other.is_null() => {
map.serialize_entry("data", other)?;
}
_ => {} }
map.end()
}
}
}
}
impl Content {
#[must_use]
pub fn as_text(&self) -> Option<&str> {
match self {
Self::Text { text: Some(t), .. } if !t.is_empty() => Some(t),
_ => None,
}
}
#[must_use]
pub fn annotations(&self) -> Option<&[Annotation]> {
match self {
Self::Text {
annotations: Some(annots),
..
} if !annots.is_empty() => Some(annots),
_ => None,
}
}
#[must_use]
pub const fn is_text(&self) -> bool {
matches!(self, Self::Text { .. })
}
#[must_use]
pub const fn is_image(&self) -> bool {
matches!(self, Self::Image { .. })
}
#[must_use]
pub const fn is_audio(&self) -> bool {
matches!(self, Self::Audio { .. })
}
#[must_use]
pub const fn is_video(&self) -> bool {
matches!(self, Self::Video { .. })
}
#[must_use]
pub const fn is_document(&self) -> bool {
matches!(self, Self::Document { .. })
}
#[must_use]
pub const fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown { .. })
}
#[must_use]
pub fn unknown_content_type(&self) -> Option<&str> {
match self {
Self::Unknown { content_type, .. } => Some(content_type),
_ => None,
}
}
#[must_use]
pub fn unknown_data(&self) -> Option<&serde_json::Value> {
match self {
Self::Unknown { data, .. } => Some(data),
_ => None,
}
}
#[must_use]
pub fn text(text: impl Into<String>) -> Self {
Self::Text {
text: Some(text.into()),
annotations: None,
}
}
#[must_use]
pub fn image_data(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
Self::Image {
data: Some(data.into()),
uri: None,
mime_type: Some(mime_type.into()),
resolution: None,
}
}
#[must_use]
pub fn image_data_with_resolution(
data: impl Into<String>,
mime_type: impl Into<String>,
resolution: Resolution,
) -> Self {
Self::Image {
data: Some(data.into()),
uri: None,
mime_type: Some(mime_type.into()),
resolution: Some(resolution),
}
}
#[must_use]
pub fn image_uri(uri: impl Into<String>, mime_type: impl Into<String>) -> Self {
Self::Image {
data: None,
uri: Some(uri.into()),
mime_type: Some(mime_type.into()),
resolution: None,
}
}
#[must_use]
pub fn image_uri_with_resolution(
uri: impl Into<String>,
mime_type: impl Into<String>,
resolution: Resolution,
) -> Self {
Self::Image {
data: None,
uri: Some(uri.into()),
mime_type: Some(mime_type.into()),
resolution: Some(resolution),
}
}
#[must_use]
pub fn audio_data(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
Self::Audio {
data: Some(data.into()),
uri: None,
mime_type: Some(mime_type.into()),
sample_rate: None,
channels: None,
}
}
#[must_use]
pub fn audio_uri(uri: impl Into<String>, mime_type: impl Into<String>) -> Self {
Self::Audio {
data: None,
uri: Some(uri.into()),
mime_type: Some(mime_type.into()),
sample_rate: None,
channels: None,
}
}
#[must_use]
pub fn video_data(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
Self::Video {
data: Some(data.into()),
uri: None,
mime_type: Some(mime_type.into()),
resolution: None,
}
}
#[must_use]
pub fn video_data_with_resolution(
data: impl Into<String>,
mime_type: impl Into<String>,
resolution: Resolution,
) -> Self {
Self::Video {
data: Some(data.into()),
uri: None,
mime_type: Some(mime_type.into()),
resolution: Some(resolution),
}
}
#[must_use]
pub fn video_uri(uri: impl Into<String>, mime_type: impl Into<String>) -> Self {
Self::Video {
data: None,
uri: Some(uri.into()),
mime_type: Some(mime_type.into()),
resolution: None,
}
}
#[must_use]
pub fn video_uri_with_resolution(
uri: impl Into<String>,
mime_type: impl Into<String>,
resolution: Resolution,
) -> Self {
Self::Video {
data: None,
uri: Some(uri.into()),
mime_type: Some(mime_type.into()),
resolution: Some(resolution),
}
}
#[must_use]
pub fn document_data(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
Self::Document {
data: Some(data.into()),
uri: None,
mime_type: Some(mime_type.into()),
}
}
#[must_use]
pub fn document_uri(uri: impl Into<String>, mime_type: impl Into<String>) -> Self {
Self::Document {
data: None,
uri: Some(uri.into()),
mime_type: Some(mime_type.into()),
}
}
#[must_use]
pub fn from_uri_and_mime(uri: impl Into<String>, mime_type: impl Into<String>) -> Self {
let uri_str = uri.into();
let mime_str = mime_type.into();
if mime_str.starts_with("image/") {
Self::Image {
data: None,
uri: Some(uri_str),
mime_type: Some(mime_str),
resolution: None,
}
} else if mime_str.starts_with("audio/") {
Self::Audio {
data: None,
uri: Some(uri_str),
mime_type: Some(mime_str),
sample_rate: None,
channels: None,
}
} else if mime_str.starts_with("video/") {
Self::Video {
data: None,
uri: Some(uri_str),
mime_type: Some(mime_str),
resolution: None,
}
} else {
Self::Document {
data: None,
uri: Some(uri_str),
mime_type: Some(mime_str),
}
}
}
#[must_use]
pub fn from_file(file: &crate::http::files::FileMetadata) -> Self {
Self::from_uri_and_mime(file.uri.clone(), file.mime_type.clone())
}
#[must_use]
pub fn with_resolution(self, resolution: Resolution) -> Self {
match self {
Self::Image {
data,
uri,
mime_type,
..
} => Self::Image {
data,
uri,
mime_type,
resolution: Some(resolution),
},
Self::Video {
data,
uri,
mime_type,
..
} => Self::Video {
data,
uri,
mime_type,
resolution: Some(resolution),
},
other => {
tracing::warn!(
"with_resolution() called on content type that doesn't support resolution. \
Resolution is only applicable to Image and Video content."
);
other
}
}
}
}
impl<'de> Deserialize<'de> for Content {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[cfg(feature = "strict-unknown")]
use serde::de::Error as _;
let value = serde_json::Value::deserialize(deserializer)?;
#[derive(Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum KnownContent {
Text {
text: Option<String>,
#[serde(default)]
annotations: Option<Vec<Annotation>>,
},
Image {
data: Option<String>,
uri: Option<String>,
mime_type: Option<String>,
resolution: Option<Resolution>,
},
Audio {
data: Option<String>,
uri: Option<String>,
mime_type: Option<String>,
#[serde(default)]
sample_rate: Option<u32>,
#[serde(default)]
channels: Option<u32>,
},
Video {
data: Option<String>,
uri: Option<String>,
mime_type: Option<String>,
resolution: Option<Resolution>,
},
Document {
data: Option<String>,
uri: Option<String>,
mime_type: Option<String>,
},
}
match serde_json::from_value::<KnownContent>(value.clone()) {
Ok(known) => Ok(match known {
KnownContent::Text { text, annotations } => Content::Text { text, annotations },
KnownContent::Image {
data,
uri,
mime_type,
resolution,
} => Content::Image {
data,
uri,
mime_type,
resolution,
},
KnownContent::Audio {
data,
uri,
mime_type,
sample_rate,
channels,
} => Content::Audio {
data,
uri,
mime_type,
sample_rate,
channels,
},
KnownContent::Video {
data,
uri,
mime_type,
resolution,
} => Content::Video {
data,
uri,
mime_type,
resolution,
},
KnownContent::Document {
data,
uri,
mime_type,
} => Content::Document {
data,
uri,
mime_type,
},
}),
Err(parse_error) => {
let content_type = value
.get("type")
.and_then(|v| v.as_str())
.unwrap_or("<missing type>")
.to_string();
tracing::warn!(
"Encountered unknown Content type '{}'. \
Parse error: {}. \
This may indicate a new API feature or a malformed response. \
The content will be preserved in the Unknown variant.",
content_type,
parse_error
);
#[cfg(feature = "strict-unknown")]
{
Err(D::Error::custom(format!(
"Unknown Content type '{}'. \
Strict mode is enabled via the 'strict-unknown' feature flag. \
Either update the library or disable strict mode.",
content_type
)))
}
#[cfg(not(feature = "strict-unknown"))]
{
Ok(Content::Unknown {
content_type,
data: value,
})
}
}
}
}
}