use super::{serialize_option_vec_string, Data};
use std::{borrow::Cow, mem, ops::Deref};
#[cfg(feature = "cli")]
use clap::ValueEnum;
use lifetime::IntoStatic;
use serde::{Serialize, Serializer};
use crate::error::{Error, Result};
#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Hash)]
#[cfg_attr(feature = "cli", derive(ValueEnum))]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum Level {
#[default]
Default,
Picky,
}
impl Level {
#[must_use]
pub fn is_default(&self) -> bool {
*self == Level::default()
}
}
#[must_use]
pub fn split_len<'source>(s: &'source str, n: usize, pat: &str) -> Vec<&'source str> {
let mut vec: Vec<&'source str> = Vec::with_capacity(s.len() / n);
let mut splits = s.split_inclusive(pat);
let mut start = 0;
let mut i = 0;
if let Some(split) = splits.next() {
vec.push(split);
} else {
return Vec::new();
}
for split in splits {
let new_len = vec[i].len() + split.len();
if new_len < n {
vec[i] = &s[start..start + new_len];
} else {
vec.push(split);
start += vec[i].len();
i += 1;
}
}
vec
}
pub const DEFAULT_LANGUAGE: &str = "auto";
fn serialize_language<S>(lang: &str, s: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
s.serialize_str(if lang.is_empty() {
DEFAULT_LANGUAGE
} else {
lang
})
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Hash, IntoStatic)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct Request<'source> {
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<Cow<'source, str>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Data<'source>>,
#[serde(serialize_with = "serialize_language")]
pub language: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub username: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub api_key: Option<String>,
#[serde(serialize_with = "serialize_option_vec_string")]
pub dicts: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mother_tongue: Option<String>,
#[serde(serialize_with = "serialize_option_vec_string")]
pub preferred_variants: Option<Vec<String>>,
#[serde(serialize_with = "serialize_option_vec_string")]
pub enabled_rules: Option<Vec<String>>,
#[serde(serialize_with = "serialize_option_vec_string")]
pub disabled_rules: Option<Vec<String>>,
#[serde(serialize_with = "serialize_option_vec_string")]
pub enabled_categories: Option<Vec<String>>,
#[serde(serialize_with = "serialize_option_vec_string")]
pub disabled_categories: Option<Vec<String>>,
#[serde(skip_serializing_if = "std::ops::Not::not")]
pub enabled_only: bool,
#[serde(skip_serializing_if = "Level::is_default")]
pub level: Level,
}
impl<'source> Request<'source> {
#[must_use]
pub fn new() -> Self {
Self {
language: "auto".to_string(),
..Default::default()
}
}
#[must_use]
pub fn with_text<T: Into<Cow<'source, str>>>(mut self, text: T) -> Self {
self.text = Some(text.into());
self.data = None;
self
}
#[must_use]
pub fn with_data(mut self, data: Data<'source>) -> Self {
self.data = Some(data);
self.text = None;
self
}
pub fn with_data_str(self, data: &str) -> serde_json::Result<Self> {
serde_json::from_str(data).map(|data| self.with_data(data))
}
#[must_use]
pub fn with_language(mut self, language: String) -> Self {
self.language = language;
self
}
pub fn try_get_text(&self) -> Result<Cow<'source, str>> {
if let Some(ref text) = self.text {
Ok(text.clone())
} else if let Some(ref data) = self.data {
match data.annotation.len() {
0 => Ok(Default::default()),
1 => data.annotation[0].try_get_text(),
_ => {
let mut text = String::new();
for da in data.annotation.iter() {
text.push_str(da.try_get_text()?.deref());
}
Ok(Cow::Owned(text))
},
}
} else {
Err(Error::InvalidRequest(
"missing either text or data field".to_string(),
))
}
}
#[must_use]
pub fn get_text(&self) -> Cow<'source, str> {
self.try_get_text().unwrap()
}
pub fn try_split(mut self, n: usize, pat: &str) -> Result<Vec<Self>> {
if let Some(data) = mem::take(&mut self.data) {
return Ok(data
.split(n, pat)
.into_iter()
.map(|d| self.clone().with_data(d))
.collect());
}
let text = mem::take(&mut self.text)
.ok_or_else(|| Error::InvalidRequest("missing text or data field".to_string()))?;
let string: &str = match &text {
Cow::Owned(s) => s.as_str(),
Cow::Borrowed(s) => s,
};
Ok(split_len(string, n, pat)
.iter()
.map(|text_fragment| {
self.clone()
.with_text(Cow::Owned(text_fragment.to_string()))
})
.collect())
}
#[must_use]
pub fn split(self, n: usize, pat: &str) -> Vec<Self> {
self.try_split(n, pat).unwrap()
}
}
#[cfg(test)]
mod tests {
use crate::api::check::DataAnnotation;
use super::*;
#[test]
fn test_with_text() {
let req = Request::default().with_text("hello");
assert_eq!(req.text.unwrap(), "hello");
assert!(req.data.is_none());
}
#[test]
fn test_with_data() {
let req =
Request::default().with_data([DataAnnotation::new_text("hello")].into_iter().collect());
assert_eq!(
req.data.unwrap().annotation[0],
DataAnnotation::new_text("hello")
);
}
#[test]
fn test_with_data_str() {
let req = Request::default()
.with_data_str("{\"annotation\":[{\"text\": \"hello\"}]}")
.unwrap();
assert_eq!(
req.data.unwrap().annotation[0],
DataAnnotation::new_text("hello")
);
assert!(Request::default().with_data_str("hello").is_err());
}
#[test]
fn test_with_language() {
assert_eq!(
Request::default().with_language("en-US".into()).language,
"en-US".to_string()
);
}
}