use std::collections::HashMap;
use std::future::Future;
use http::Method;
use serde::de::IntoDeserializer;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use crate::body::XMLBody;
use crate::error::Result;
use crate::response::BodyResponseProcessor;
use crate::ser::OnlyKeyField;
use crate::{Client, Ops, Prepared, Request};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum DoMetaQueryMode {
Basic,
Semantic,
}
impl Default for DoMetaQueryMode {
fn default() -> Self {
Self::Basic
}
}
impl AsRef<str> for DoMetaQueryMode {
fn as_ref(&self) -> &str {
match self {
DoMetaQueryMode::Basic => "basic",
DoMetaQueryMode::Semantic => "semantic",
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SortOrder {
Asc,
Desc,
}
impl Default for SortOrder {
fn default() -> Self {
Self::Asc
}
}
impl AsRef<str> for SortOrder {
fn as_ref(&self) -> &str {
match self {
SortOrder::Asc => "asc",
SortOrder::Desc => "desc",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MediaType {
Image,
Video,
Audio,
Document,
}
impl AsRef<str> for MediaType {
fn as_ref(&self) -> &str {
match self {
MediaType::Image => "image",
MediaType::Video => "video",
MediaType::Audio => "audio",
MediaType::Document => "document",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AggregationOperation {
Sum,
Avg,
Max,
Min,
Count,
Group,
}
impl AsRef<str> for AggregationOperation {
fn as_ref(&self) -> &str {
match self {
AggregationOperation::Sum => "sum",
AggregationOperation::Avg => "avg",
AggregationOperation::Max => "max",
AggregationOperation::Min => "min",
AggregationOperation::Count => "count",
AggregationOperation::Group => "group",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Aggregation {
pub field: String,
pub operation: AggregationOperation,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct AggregationResult {
pub field: String,
pub operation: String,
pub value: Option<String>,
#[serde(default)]
pub groups: Vec<AggregationGroup>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct AggregationGroup {
pub value: String,
pub count: u64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Address {
pub address_line: Option<String>,
pub city: Option<String>,
pub country: Option<String>,
pub district: Option<String>,
pub language: Option<String>,
pub province: Option<String>,
pub township: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct VideoStream {
pub codec_name: Option<String>,
pub language: Option<String>,
pub bitrate: Option<String>,
pub frame_rate: Option<String>,
pub start_time: Option<String>,
pub duration: Option<String>,
pub frame_count: Option<String>,
pub bit_depth: Option<String>,
pub pixel_format: Option<String>,
pub color_space: Option<String>,
pub height: Option<u32>,
pub width: Option<u32>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct AudioStream {
pub codec_name: Option<String>,
pub bitrate: Option<String>,
pub sample_rate: Option<String>,
pub start_time: Option<String>,
pub duration: Option<String>,
pub channels: Option<String>,
pub language: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Subtitle {
pub codec_name: Option<String>,
pub language: Option<String>,
pub start_time: Option<String>,
pub duration: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct FileInfo {
pub filename: String,
pub size: u64,
pub file_modified_time: Option<String>,
#[serde(rename = "OSSObjectLastModifiedTime")]
pub oss_object_last_modified_time: Option<String>,
#[serde(rename = "ETag")]
pub etag: Option<String>,
#[serde(rename = "OSSCRC64")]
pub oss_crc64: Option<String>,
pub produce_time: Option<String>,
pub content_type: Option<String>,
pub media_type: Option<String>,
pub lat_long: Option<String>,
pub title: Option<String>,
#[serde(rename = "OSSExpiration")]
pub oss_expiration: Option<String>,
pub cache_control: Option<String>,
pub content_disposition: Option<String>,
pub content_encoding: Option<String>,
pub content_language: Option<String>,
pub access_control_allow_origin: Option<String>,
pub access_control_request_method: Option<String>,
pub server_side_data_encryption: Option<String>,
pub server_side_encryption_key_id: Option<String>,
pub image_height: Option<u32>,
pub image_width: Option<u32>,
pub video_width: Option<u32>,
pub video_height: Option<u32>,
pub bitrate: Option<String>,
pub artist: Option<String>,
pub album_artist: Option<String>,
pub composer: Option<String>,
pub performer: Option<String>,
pub album: Option<String>,
pub duration: Option<String>,
#[serde(rename = "OSSObjectType")]
pub oss_object_type: Option<String>,
#[serde(rename = "OSSStorageClass")]
pub oss_storage_class: Option<String>,
#[serde(rename = "OSSTaggingCount")]
pub oss_tagging_count: Option<u32>,
#[serde(default)]
pub video_streams: Vec<VideoStream>,
#[serde(default)]
pub audio_streams: Vec<AudioStream>,
#[serde(default)]
pub subtitles: Vec<Subtitle>,
#[serde(default)]
pub addresses: Vec<Address>,
#[serde(rename = "OSSTagging", default, deserialize_with = "unwrap_tagging")]
pub tagging: HashMap<String, String>,
#[serde(rename = "OSSUserMeta", default, deserialize_with = "unwrap_user_meta")]
pub user_meta: HashMap<String, String>,
}
#[skip_serializing_none]
#[derive(Debug, Clone, Serialize, Default)]
#[serde(rename = "MetaQuery", rename_all = "PascalCase")]
pub struct MetaQueryBody {
pub next_token: Option<String>,
pub max_results: Option<u32>,
pub query: Option<String>,
pub simple_query: Option<String>,
pub sort: Option<String>,
pub order: Option<SortOrder>,
pub aggregations: Option<Vec<Aggregation>>,
pub media_types: Option<Vec<MediaType>>,
}
fn unwrap_files<'de, D>(deserializer: D) -> std::result::Result<Vec<FileInfo>, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
struct Files {
#[serde(default)]
file: Vec<FileInfo>,
}
Ok(Files::deserialize(deserializer)?.file)
}
fn unwrap_aggregations<'de, D>(deserializer: D) -> std::result::Result<Vec<AggregationResult>, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
struct Aggregations {
#[serde(default)]
aggregation: Vec<AggregationResult>,
}
Ok(Aggregations::deserialize(deserializer)?.aggregation)
}
fn unwrap_user_meta<'de, D>(de: D) -> std::result::Result<HashMap<String, String>, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct UserMeta {
pub key: String,
pub value: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
struct OSSUserMeta {
#[serde(default)]
user_meta: Vec<UserMeta>,
}
Ok(OSSUserMeta::deserialize(de)?
.user_meta
.into_iter()
.map(|meta| {
(
meta.key
.to_lowercase()
.trim_start_matches("x-oss-meta-")
.to_string(),
meta.value,
)
})
.collect())
}
fn unwrap_tagging<'de, D>(de: D) -> std::result::Result<HashMap<String, String>, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct Tagging {
pub key: String,
pub value: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
struct OSSTagging {
#[serde(default)]
tagging: Vec<Tagging>,
}
Ok(OSSTagging::deserialize(de)?
.tagging
.into_iter()
.map(|t| (t.key, t.value))
.collect())
}
fn empty_string_as_none<'de, D, T>(de: D) -> std::result::Result<Option<T>, D::Error>
where
D: serde::Deserializer<'de>,
T: serde::Deserialize<'de>,
{
let opt = Option::<String>::deserialize(de)?;
let opt = opt.as_deref();
match opt {
None | Some("") => Ok(None),
Some(s) => T::deserialize(s.into_deserializer()).map(Some),
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct MetaQueryResponse {
#[serde(deserialize_with = "empty_string_as_none")]
pub next_token: Option<String>,
#[serde(default, deserialize_with = "unwrap_files")]
pub files: Vec<FileInfo>,
#[serde(default, deserialize_with = "unwrap_aggregations")]
pub aggregations: Vec<AggregationResult>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DoMetaQueryParams {
pub mode: DoMetaQueryMode,
pub comp: String,
meta_query: OnlyKeyField,
}
impl Default for DoMetaQueryParams {
fn default() -> Self {
Self {
meta_query: OnlyKeyField,
mode: DoMetaQueryMode::Basic,
comp: "query".to_string(),
}
}
}
pub struct DoMetaQuery {
pub mode: DoMetaQueryMode,
pub body: MetaQueryBody,
pub query: DoMetaQueryParams,
}
impl Ops for DoMetaQuery {
type Response = BodyResponseProcessor<MetaQueryResponse>;
type Body = XMLBody<MetaQueryBody>;
type Query = DoMetaQueryParams;
fn prepare(self) -> Result<Prepared<DoMetaQueryParams, MetaQueryBody>> {
Ok(Prepared {
method: Method::POST,
query: Some(self.query),
body: Some(self.body),
..Default::default()
})
}
}
pub trait DataIndexingOperations {
fn do_meta_query(
&self,
mode: DoMetaQueryMode,
body: MetaQueryBody,
query: DoMetaQueryParams,
) -> impl Future<Output = Result<MetaQueryResponse>>;
}
impl DataIndexingOperations for Client {
async fn do_meta_query(
&self,
mode: DoMetaQueryMode,
body: MetaQueryBody,
query: DoMetaQueryParams,
) -> Result<MetaQueryResponse> {
let ops = DoMetaQuery { mode, body, query };
self.request(ops).await
}
}
#[derive(Debug, Clone)]
pub struct QueryBuilder {
conditions: Vec<QueryCondition>,
}
#[derive(Debug, Clone)]
pub struct QueryCondition {
pub field: String,
pub operation: String,
pub value: String,
}
impl QueryBuilder {
pub fn new() -> Self {
Self {
conditions: Vec::new(),
}
}
pub fn eq(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
self.conditions.push(QueryCondition {
field: field.into(),
operation: "eq".to_string(),
value: value.into(),
});
self
}
pub fn gt(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
self.conditions.push(QueryCondition {
field: field.into(),
operation: "gt".to_string(),
value: value.into(),
});
self
}
pub fn gte(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
self.conditions.push(QueryCondition {
field: field.into(),
operation: "gte".to_string(),
value: value.into(),
});
self
}
pub fn lt(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
self.conditions.push(QueryCondition {
field: field.into(),
operation: "lt".to_string(),
value: value.into(),
});
self
}
pub fn lte(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
self.conditions.push(QueryCondition {
field: field.into(),
operation: "lte".to_string(),
value: value.into(),
});
self
}
pub fn prefix(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
self.conditions.push(QueryCondition {
field: field.into(),
operation: "prefix".to_string(),
value: value.into(),
});
self
}
pub fn r#match(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
self.conditions.push(QueryCondition {
field: field.into(),
operation: "match".to_string(),
value: value.into(),
});
self
}
pub fn build(self) -> Result<String> {
if self.conditions.is_empty() {
return Err(crate::error::Error::InvalidArgument(
"Query conditions cannot be empty".to_string(),
));
}
if self.conditions.len() == 1 {
let condition = &self.conditions[0];
let query = serde_json::json!({
"Field": condition.field,
"Operation": condition.operation,
"Value": condition.value
});
Ok(query.to_string())
} else {
let sub_queries: Vec<_> = self
.conditions
.iter()
.map(|c| {
serde_json::json!({
"Field": c.field,
"Operation": c.operation,
"Value": c.value
})
})
.collect();
let query = serde_json::json!({
"Operation": "and",
"SubQueries": sub_queries
});
Ok(query.to_string())
}
}
}
impl Default for QueryBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct MetaQueryRequestBuilder {
request: MetaQueryBody,
}
impl MetaQueryRequestBuilder {
pub fn new() -> Self {
Self {
request: MetaQueryBody {
next_token: None,
max_results: None,
query: None,
simple_query: None,
sort: None,
order: None,
aggregations: None,
media_types: None,
},
}
}
pub fn next_token(mut self, token: impl Into<String>) -> Self {
self.request.next_token = Some(token.into());
self
}
pub fn max_results(mut self, max: u32) -> Self {
self.request.max_results = Some(max);
self
}
pub fn query(mut self, query: impl Into<String>) -> Self {
self.request.query = Some(query.into());
self
}
pub fn simple_query(mut self, query: impl Into<String>) -> Self {
self.request.simple_query = Some(query.into());
self
}
pub fn sort(mut self, field: impl Into<String>) -> Self {
self.request.sort = Some(field.into());
self
}
pub fn order(mut self, order: SortOrder) -> Self {
self.request.order = Some(order);
self
}
pub fn aggregation(mut self, field: impl Into<String>, operation: AggregationOperation) -> Self {
let aggregations = self.request.aggregations.get_or_insert_with(Vec::new);
aggregations.push(Aggregation {
field: field.into(),
operation,
});
self
}
pub fn media_types(mut self, types: Vec<MediaType>) -> Self {
self.request.media_types = Some(types);
self
}
pub fn build(self) -> MetaQueryBody {
self.request
}
}
impl Default for MetaQueryRequestBuilder {
fn default() -> Self {
Self::new()
}
}