use std::io;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use specta::Type;
use thiserror::Error;
use aves::bus::DispatchContextError;
use aves::runtime::GeneratedCallError;
use aves::utils::errors::SharedInfraError;
use aves::utils::uuid::SpectaUuid;
pub type CreateKnowledgeRequest = VaultCreateKnowledgeRequest;
pub type GetKnowledgeRequest = String;
pub type UpdateKnowledgeRequest = VaultUpdateKnowledgeRequest;
pub type DeleteKnowledgeRequest = String;
pub type ListKnowledgeRequest = VaultListKnowledgeRequest;
pub type SearchKnowledgeRequest = VaultSearchKnowledgeRequest;
pub type KnowledgeRecord = VaultKnowledge;
pub type KnowledgeRecordLookup = Option<VaultKnowledge>;
pub type KnowledgeRecordList = Vec<VaultKnowledge>;
pub type KnowledgeSearchOutcome = VaultKnowledgeSearchResponse;
pub type VaultKnowledgeTree = Vec<VaultKnowledgeTreeEntry>;
pub(super) type KnowledgeResult<T> = Result<T, KnowledgeError>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub enum KnowledgeContentType {
Markdown,
Plaintext,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct CreateCanvasDashboardRequest {
pub title: String,
pub html: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct GetCanvasDashboardRequest {
pub id: SpectaUuid,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct UpdateCanvasDashboardRequest {
pub id: SpectaUuid,
pub title: String,
pub html: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct DeleteCanvasDashboardRequest {
pub id: SpectaUuid,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct ListCanvasDashboardsRequest {
pub limit: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct CanvasDashboard {
pub id: SpectaUuid,
pub title: String,
pub html: String,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct CanvasDashboardSummary {
pub id: SpectaUuid,
pub title: String,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct CanvasDashboardLookup(pub Option<CanvasDashboard>);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct CanvasDashboardSummaryList(pub Vec<CanvasDashboardSummary>);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct CanvasDashboardDeleteResult {
pub deleted: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct VaultCreateKnowledgeRequest {
pub backend_id: Option<String>,
pub title: String,
pub content: Option<String>,
pub input_file: Option<PathBuf>,
pub content_type: KnowledgeContentType,
pub frontmatter: Map<String, Value>,
pub tags: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct VaultUpdateKnowledgeRequest {
pub backend_id: String,
pub path: Option<String>,
pub title: Option<String>,
pub content: Option<String>,
pub content_type: Option<KnowledgeContentType>,
pub frontmatter: Option<Map<String, Value>>,
pub tags: Option<Vec<String>>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct VaultKnowledgeFilters {
pub path_prefix: Option<String>,
pub tags: Option<Vec<String>>,
pub frontmatter: Option<Map<String, Value>>,
pub content_type: Option<KnowledgeContentType>,
pub created_after: Option<String>,
pub created_before: Option<String>,
pub updated_after: Option<String>,
pub updated_before: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct VaultListKnowledgeRequest {
pub query: Option<String>,
pub filters: VaultKnowledgeFilters,
pub limit: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct VaultSearchKnowledgeRequest {
pub query: String,
pub filters: VaultKnowledgeFilters,
pub limit: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct VaultKnowledge {
pub backend_id: String,
pub path: String,
pub title: String,
pub content: String,
pub content_type: KnowledgeContentType,
pub frontmatter: Map<String, Value>,
pub tags: Vec<String>,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub enum VaultKnowledgeTreeEntryKind {
Directory,
File,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct VaultKnowledgeTreeEntry {
pub backend_id: String,
pub path: String,
pub kind: VaultKnowledgeTreeEntryKind,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct VaultKnowledgeSearchResult {
pub knowledge: VaultKnowledge,
pub score: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct VaultKnowledgeSearchResponse {
pub query: String,
pub results: Vec<VaultKnowledgeSearchResult>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct UpsertDataRequest {
pub key: String,
pub value: Map<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct DataObject {
pub key: String,
pub value: Map<String, Value>,
pub updated_at: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct DataSummary {
pub key: String,
pub updated_at: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct DataList {
pub entries: Vec<DataSummary>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct ListDataRequest {
pub since: Option<String>,
pub until: Option<String>,
pub limit: u32,
pub offset: u32,
}
#[derive(Debug, Clone)]
pub(super) enum ResolvedCreateKnowledgeRequest {
Single {
request: VaultCreateKnowledgeRequest,
content: String,
},
DirectoryFile {
request: VaultCreateKnowledgeRequest,
content: String,
source_relative_path: PathBuf,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub struct DeleteKnowledgeResult {
pub backend_id: String,
pub deleted: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
#[serde(default)]
pub struct KnowledgeConfig {
pub root: Option<PathBuf>,
pub trash_path: PathBuf,
pub excluded_paths: Vec<String>,
pub allowed_extensions: Vec<String>,
}
impl Default for KnowledgeConfig {
fn default() -> Self {
Self {
root: None,
trash_path: PathBuf::from(".trash"),
excluded_paths: Vec::new(),
allowed_extensions: vec![
"md".to_owned(),
"markdown".to_owned(),
"txt".to_owned(),
"canvas".to_owned(),
],
}
}
}
impl KnowledgeConfig {
pub(super) fn excludes_path(&self, root: &Path, relative: &Path) -> bool {
self.excluded_paths.iter().any(|excluded| {
let excluded_path = Path::new(excluded);
if excluded_path.is_absolute() {
root.join(relative).starts_with(excluded_path)
} else if excluded.starts_with("./") || excluded.contains('/') || excluded.contains('\\') {
relative.starts_with(Path::new(excluded.strip_prefix("./").unwrap_or(excluded)))
} else {
relative
.components()
.any(|component| component.as_os_str() == excluded_path.as_os_str())
}
})
}
}
#[derive(Error, Debug, Clone, Serialize, Deserialize, Type, ::schemars::JsonSchema)]
pub enum KnowledgeError {
#[error("knowledge data not found: {key}")]
DataNotFound { key: String },
#[error("invalid knowledge data date: {value}")]
InvalidDataDate { value: String },
#[error("knowledge data {operation} failed: {message}")]
DataStorage { operation: String, message: String },
#[error("i/o error at path: {message}")]
Io {
path: String,
message: String,
},
#[error("canvas dashboard not found: {id}")]
CanvasDashboardNotFound {
id: SpectaUuid,
},
#[error(
"{backend} knowledge not found at `{identifier}`; use listKnowledge or searchKnowledge to rediscover its current identifier"
)]
KnowledgeNotFound {
backend: String,
identifier: String,
},
#[error("knowledge file is not valid UTF-8: {path}")]
InvalidTextEncoding {
path: String,
},
#[error("canvas dashboard i/o error at path {path}: {message}")]
CanvasDashboardIo {
path: String,
message: String,
},
#[error("channel send error")]
ChannelSend,
#[error("channel receive error")]
ChannelRecv,
#[error("oneshot reply channel closed")]
OneshotReply,
#[error("neither content nor input_file supplied for createKnowledge")]
MissingCreateInput,
#[error("no directory files could be imported: {path}")]
NoDirectoryFilesImported {
path: String,
},
#[error("invalid {backend} knowledge config: {message}")]
InvalidBackendConfig {
backend: String,
message: String,
},
#[error("invalid knowledge value {field}: {message}")]
InvalidStoreValue {
field: String,
value: Option<String>,
message: String,
},
#[error("knowledge metadata failed {operation}: {message}")]
MetadataSerialization {
operation: String,
message: String,
},
#[error("knowledge store failure {operation}: {message}")]
StoreConsistency {
operation: String,
message: String,
},
#[error("knowledge bus dispatch failed: {message}")]
BusDispatch { message: String },
#[error("knowledge bus authentication failed: {message}")]
BusAuthentication { message: String },
#[error("expected knowledge response {expected}, got {actual}")]
UnexpectedBusResponse { expected: String, actual: String },
#[error("{0}")]
Other(
String,
),
}
impl KnowledgeError {
pub fn rpc_identity(&self) -> (&'static str, Map<String, Value>) {
match self {
KnowledgeError::DataNotFound { key } => {
let mut details = Map::new();
details.insert("key".to_owned(), Value::String(key.clone()));
("DataNotFound", details)
}
KnowledgeError::InvalidDataDate { value } => {
let mut details = Map::new();
details.insert("value".to_owned(), Value::String(value.clone()));
("InvalidDataDate", details)
}
KnowledgeError::DataStorage { operation, .. } => {
let mut details = Map::new();
details.insert("operation".to_owned(), Value::String(operation.clone()));
("DataStorage", details)
}
KnowledgeError::Io { path, .. } => {
let mut details = Map::new();
details.insert("path".to_string(), Value::String(path.clone()));
("Io", details)
}
KnowledgeError::CanvasDashboardNotFound { id } => {
let mut details = Map::new();
details.insert("id".to_string(), Value::String(id.to_string()));
("CanvasDashboardNotFound", details)
}
KnowledgeError::KnowledgeNotFound { backend, identifier } => {
let mut details = Map::new();
details.insert("backend".to_string(), Value::String(backend.clone()));
details.insert("identifier".to_string(), Value::String(identifier.clone()));
("KnowledgeNotFound", details)
}
KnowledgeError::InvalidTextEncoding { path } => {
let mut details = Map::new();
details.insert("path".to_string(), Value::String(path.clone()));
("InvalidTextEncoding", details)
}
KnowledgeError::CanvasDashboardIo { path, .. } => {
let mut details = Map::new();
details.insert("path".to_string(), Value::String(path.clone()));
("CanvasDashboardIo", details)
}
KnowledgeError::ChannelSend => ("ChannelSend", Map::new()),
KnowledgeError::ChannelRecv => ("ChannelRecv", Map::new()),
KnowledgeError::OneshotReply => ("OneshotReply", Map::new()),
KnowledgeError::MissingCreateInput => ("MissingCreateInput", Map::new()),
KnowledgeError::NoDirectoryFilesImported { path } => {
let mut details = Map::new();
details.insert("path".to_string(), Value::String(path.clone()));
("NoDirectoryFilesImported", details)
}
KnowledgeError::InvalidBackendConfig { .. } => ("InvalidBackendConfig", Map::new()),
KnowledgeError::InvalidStoreValue { field, value, .. } => {
let mut details = Map::new();
details.insert("field".to_string(), Value::String(field.clone()));
if let Some(value) = value {
details.insert("value".to_string(), Value::String(value.clone()));
}
("InvalidStoreValue", details)
}
KnowledgeError::MetadataSerialization { operation, .. } => {
let mut details = Map::new();
details.insert("operation".to_string(), Value::String(operation.clone()));
("MetadataSerialization", details)
}
KnowledgeError::StoreConsistency { operation, .. } => {
let mut details = Map::new();
details.insert("operation".to_string(), Value::String(operation.clone()));
("StoreConsistency", details)
}
KnowledgeError::BusDispatch { .. } => ("BusDispatch", Map::new()),
KnowledgeError::BusAuthentication { .. } => ("BusAuthentication", Map::new()),
KnowledgeError::UnexpectedBusResponse { expected, actual } => {
let mut details = Map::new();
details.insert("expected".to_string(), Value::String(expected.clone()));
details.insert("actual".to_string(), Value::String(actual.clone()));
("UnexpectedBusResponse", details)
}
KnowledgeError::Other(_) => ("Other", Map::new()),
}
}
}
impl From<SharedInfraError> for KnowledgeError {
fn from(error: SharedInfraError) -> Self {
use SharedInfraError;
match error {
SharedInfraError::ChannelSend { .. } => Self::ChannelSend,
SharedInfraError::ChannelRecv { .. } => Self::ChannelRecv,
SharedInfraError::OneshotReplyTimeout { .. } => Self::ChannelRecv,
SharedInfraError::OneshotReplyMissing { .. } => Self::OneshotReply,
}
}
}
impl From<GeneratedCallError<KnowledgeError>> for KnowledgeError {
fn from(error: GeneratedCallError<KnowledgeError>) -> Self {
match error {
GeneratedCallError::DomainError(error) => error,
GeneratedCallError::Authentication(error) => Self::BusAuthentication {
message: error.to_string(),
},
GeneratedCallError::InputClosed => Self::ChannelSend,
GeneratedCallError::ReplyDropped => Self::OneshotReply,
GeneratedCallError::UnexpectedResponse { expected, actual } => {
Self::UnexpectedBusResponse { expected, actual }
}
}
}
}
impl From<DispatchContextError> for KnowledgeError {
fn from(error: DispatchContextError) -> Self {
Self::BusDispatch {
message: error.to_string(),
}
}
}
impl From<serde_json::Error> for KnowledgeError {
fn from(error: serde_json::Error) -> Self {
Self::MetadataSerialization {
operation: "serialize knowledge RPC".to_string(),
message: error.to_string(),
}
}
}
impl rspc::Error for KnowledgeError {
fn into_procedure_error(self) -> rspc::ProcedureError {
let (code, details) = self.rpc_identity();
let safe_error = serde_json::json!({
"DomainError": {
"domain": "knowledge",
"code": code,
"message": self.to_string(),
"details": details,
}
});
rspc::ProcedureError::Resolver(rspc::ResolverError::new(safe_error, None::<Self>))
}
}
impl From<io::Error> for KnowledgeError {
fn from(err: io::Error) -> Self {
KnowledgeError::Io {
path: String::new(),
message: err.to_string(),
}
}
}