mod accessors;
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod envelope_tests;
use serde::{Deserialize, Serialize};
use crate::constants::DEFAULT_LIMIT;
use crate::error::{ErrorCode, TalonError, TalonResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(try_from = "u16", into = "u16")]
pub struct PositiveCount(u16);
impl Default for PositiveCount {
fn default() -> Self {
Self(DEFAULT_LIMIT)
}
}
impl PositiveCount {
pub fn new(value: u16, field: &'static str) -> TalonResult<Self> {
if value == 0 {
return Err(TalonError::InvalidInput {
field,
message: "must be greater than zero".to_string(),
});
}
Ok(Self(value))
}
#[must_use]
pub const fn get(self) -> u16 {
self.0
}
pub(crate) const fn from_const(value: u16) -> Self {
Self(value)
}
}
impl TryFrom<u16> for PositiveCount {
type Error = TalonError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
Self::new(value, "count")
}
}
impl From<PositiveCount> for u16 {
fn from(value: PositiveCount) -> Self {
value.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct VaultPath(String);
impl VaultPath {
pub fn parse(value: impl Into<String>) -> TalonResult<Self> {
let value = value.into();
if value.trim().is_empty() {
return Err(TalonError::InvalidInput {
field: "path",
message: "must not be empty".to_string(),
});
}
Ok(Self(value))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ContainerPath(String);
impl ContainerPath {
pub fn parse(value: impl Into<String>) -> TalonResult<Self> {
let value = value.into();
if value.trim().is_empty() {
return Err(TalonError::InvalidInput {
field: "path",
message: "must not be empty".to_string(),
});
}
Ok(Self(value))
}
#[must_use]
pub fn root() -> Self {
Self("/".to_string())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResponseMeta {
pub duration_ms: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub result_count: Option<u32>,
#[serde(default)]
pub warnings: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope_set: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub since: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ErrorEnvelope {
pub code: ErrorCode,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<serde_json::Value>,
}
use crate::indexing::{InspectResponse, StatusResponse, SyncResponse};
use crate::query::{ChangesResponse, MetaResponse, ReadResponse, RecallResponse, RelatedResponse};
use crate::search::SearchResponse;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "action", rename_all = "kebab-case")]
pub enum TalonResponseData {
Search(SearchResponse),
Ask(crate::query::AskResponse),
Read(ReadResponse),
Sync(SyncResponse),
Status(StatusResponse),
Related(RelatedResponse),
Meta(MetaResponse),
Changes(ChangesResponse),
Inspect(InspectResponse),
Recall(RecallResponse),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TalonEnvelope {
pub action: String,
pub version: String,
pub ok: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<TalonResponseData>,
#[serde(skip_serializing_if = "Option::is_none")]
pub meta: Option<ResponseMeta>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<ErrorEnvelope>,
}
impl TalonEnvelope {
#[must_use]
pub fn ok(action: &'static str, data: TalonResponseData, meta: ResponseMeta) -> Self {
Self {
action: action.to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
ok: true,
data: Some(data),
meta: Some(meta),
error: None,
}
}
#[must_use]
pub fn err(action: &str, error: ErrorEnvelope) -> Self {
Self {
action: action.to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
ok: false,
data: None,
meta: None,
error: Some(error),
}
}
#[must_use]
pub const fn data(&self) -> Option<&TalonResponseData> {
self.data.as_ref()
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn data_mut(&mut self) -> Option<&mut TalonResponseData> {
self.data.as_mut()
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn into_data(self) -> Option<TalonResponseData> {
self.data
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn as_response(&self) -> Option<&dyn TalonResponseTrait> {
self.data.as_ref().map(|d| d as &dyn TalonResponseTrait)
}
}
pub trait TalonResponseTrait {
fn action(&self) -> &str;
}
use crate::indexing::{InspectInput, StatusInput, SyncInput};
use crate::query::{ChangesInput, MetaInput, ReadInput, RecallInput, RelatedInput};
use crate::search::SearchInput;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "action", rename_all = "kebab-case")]
pub enum TalonInput {
Search(SearchInput),
Read(ReadInput),
Sync(SyncInput),
Status(StatusInput),
Related(RelatedInput),
Meta(MetaInput),
Changes(ChangesInput),
Inspect(InspectInput),
Recall(RecallInput),
}