use std::error::Error;
use serde::{Deserialize, Serialize};
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Retina {
#[serde(rename = "retinaName")]
pub retina_name: String,
#[serde(rename = "numberOfColumns")]
pub number_of_columns: i64,
#[serde(rename = "numberOfTermsInRetina")]
pub number_of_terms_in_retina: i64,
pub description: String,
#[serde(rename = "numberOfRows")]
pub number_of_rows: i64,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Fingerprint {
pub positions: Vec<i32>,
}
impl Fingerprint {
pub fn expand(&self, len: usize) -> Vec<u8> {
let mut expanded = vec![0; len];
for pos in &self.positions {
expanded[*pos as usize] = 1;
}
expanded
}
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TextSlice {
pub text: String,
pub fingerprint: Option<Fingerprint>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TextSliceRequest {
pub retina_name: String,
pub start_index: usize,
pub max_results: usize,
pub get_fingerprint: bool,
}
impl TextSliceRequest {
pub fn new() -> Self {
Self {
retina_name: "en_general".to_string(),
start_index: 0,
max_results: 10,
get_fingerprint: false,
}
}
pub fn with_retina_name(mut self, retina_name: &str) -> Self {
self.retina_name = retina_name.to_string();
self
}
pub fn with_start_index(mut self, start_index: usize) -> Self {
self.start_index = start_index;
self
}
pub fn with_max_results(mut self, max_results: usize) -> Self {
self.max_results = max_results;
self
}
pub fn with_get_fingerprint(mut self, get_fingerprint: bool) -> Self {
self.get_fingerprint = get_fingerprint;
self
}
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TextEnvelope {
pub text: String,
}
impl TextEnvelope {
pub fn new(text: &str) -> Self {
Self {
text: text.to_string(),
}
}
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CreateCategoryFilterRequest {
pub category_name: Option<String>,
pub positive_examples: Vec<TextEnvelope>,
pub negative_examples: Vec<TextEnvelope>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LanguageResponse {
pub language: Option<String>,
pub iso_tag: Option<String>,
pub wiki_url: Option<String>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CompareResponse {
#[serde(rename = "sizeLeft")]
pub size_left: i64,
#[serde(rename = "sizeRight")]
pub size_right: i64,
#[serde(rename = "weightedScoring")]
pub weighted_scoring: f64,
#[serde(rename = "euclideanDistance")]
pub euclidean_distance: f64,
#[serde(rename = "jaccardDistance")]
pub jaccard_distance: f64,
#[serde(rename = "overlappingAll")]
pub overlapping_all: i64,
#[serde(rename = "overlappingLeftRight")]
pub overlapping_left_right: f64,
#[serde(rename = "overlappingRightLeft")]
pub overlapping_right_left: f64,
#[serde(rename = "cosineSimilarity")]
pub cosine_similarity: f64,
}
pub struct Cortical {
pub client: reqwest::Client,
}
impl Cortical {
pub fn new() -> Cortical {
Cortical {
client: reqwest::Client::new(),
}
}
pub async fn get_retinas(&self) -> Result<Vec<Retina>, Box<dyn Error>> {
let response =
self.client
.get("https://languages.cortical.io/rest/retinas")
.send()
.await?;
Ok(
response
.json()
.await?
)
}
pub async fn get_text_analysis(
&self,
text: &str,
retina_name: Option<&str>,
) -> Result<Vec<Fingerprint>, Box<dyn Error>> {
let retina_name = retina_name.unwrap_or("en_general");
let response =
self.client
.post(format!("https://languages.cortical.io/rest/text?retina_name={}", retina_name))
.header("Accept", "application/json")
.header("Referer", "")
.header("Content-Type", "application/json")
.body(text.to_string())
.send()
.await?;
Ok(
response
.json()
.await?
)
}
pub async fn get_text_keywords(
&self,
text: &str,
retina_name: Option<&str>,
) -> Result<Vec<String>, Box<dyn Error>> {
let retina_name = retina_name.unwrap_or("en_general");
let response =
self.client
.post(format!("https://languages.cortical.io/rest/text/keywords?retina_name={}", retina_name))
.header("Accept", "application/json")
.header("Referer", "")
.header("Content-Type", "text/plain;charset=UTF-8")
.body(text.to_string())
.send()
.await?;
Ok(
response
.json()
.await?
)
}
pub async fn get_text_slices(
&self,
text: &str,
params: Option<TextSliceRequest>,
) -> Result<Vec<TextSlice>, Box<dyn Error>> {
let params = params.unwrap_or_default();
let response =
self.client
.post(
format!(
"https://languages.cortical.io/rest/text/slices?retina_name={}&start_index={}&max_results={}&get_fingerprint={}",
params.retina_name,
params.start_index,
params.max_results,
params.get_fingerprint
)
)
.header("Accept", "application/json")
.header("Referer", "")
.header("Content-Type", "application/json")
.body(text.to_string())
.send()
.await?;
Ok(
response
.json()
.await?
)
}
pub async fn get_text_detect_language(
&self,
text: &str,
) -> Result<LanguageResponse, Box<dyn Error>> {
let response =
self.client
.post("https://languages.cortical.io/rest/text/detect_language")
.header("Accept", "application/json")
.header("Referer", "")
.header("Content-Type", "application/json")
.body(text.to_string())
.send()
.await?;
Ok(
response
.json()
.await?
)
}
pub async fn create_category_filter(
&self,
positive_examples: Vec<String>,
negative_examples: Vec<String>,
retina_name: Option<&str>,
) -> Result<(), Box<dyn Error>> {
let retina_name = retina_name.unwrap_or("en_general");
let positive_examples = positive_examples
.into_iter()
.map(|text| TextEnvelope { text })
.collect();
let negative_examples = negative_examples
.into_iter()
.map(|text| TextEnvelope { text })
.collect();
let request = CreateCategoryFilterRequest {
category_name: None,
positive_examples,
negative_examples,
};
Ok(
self.client
.post(
format!(
"https://languages.cortical.io/rest/classify/create_category_filter?retina_name={}&filter_name={}",
retina_name,
"filter_name"
)
)
.header("Accept", "application/json")
.header("Referer", "")
.header("Content-Type", "application/json")
.body(serde_json::to_string(&request)?)
.send()
.await?
.json()
.await?
)
}
pub async fn get_compare(
&self,
(text1, text2): (&str, &str),
retina_name: Option<&str>,
) -> Result<CompareResponse, Box<dyn Error>> {
let retina_name = retina_name.unwrap_or("en_general");
Ok(
self.client
.post(format!("https://languages.cortical.io/rest/compare?retina_name={}", retina_name))
.header("Accept", "application/json")
.header("Referer", "")
.header("Content-Type", "application/json")
.body(
serde_json::to_string(
&vec![
TextEnvelope::new(text1),
TextEnvelope::new(text2),
]
)?
)
.send()
.await?
.json()
.await?
)
}
}