use std::fmt;
use serde::{Deserialize, Serialize};
pub const ITEM_SCHEMA: &str = "agd.item/v1";
pub const AGENDA_ENTRY_SCHEMA: &str = "agd.agenda-entry/v1";
pub const MUTATION_SCHEMA: &str = "agd.mutation/v1";
pub const ERROR_SCHEMA: &str = "agd.error/v1";
pub const VIEW_SECTION_SCHEMA: &str = "agd.view-section/v1";
pub const VIEWS_SCHEMA: &str = "agd.views/v1";
pub const STATUS_SCHEMA: &str = "agd.status/v1";
pub const CHECK_SCHEMA: &str = "agd.check/v1";
pub const JOURNAL_SCHEMA: &str = "agd.journal/v1";
pub const UNDO_SCHEMA: &str = "agd.undo/v1";
pub const REFILE_SCHEMA: &str = "agd.refile/v1";
pub const DESCRIBE_SCHEMA: &str = "agd.describe/v1";
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Item {
pub schema: String,
#[serde(rename = "ref")]
pub item_ref: String,
pub id: Option<String>,
pub file: String,
pub line: usize,
pub olpath: Vec<String>,
pub todo: Option<String>,
pub done: bool,
pub priority: Option<String>,
pub title: String,
pub tags: Vec<String>,
pub inherited_tags: Vec<String>,
pub category: String,
pub planning: Planning,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checklist: Option<Progress>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Progress {
pub done: usize,
pub total: usize,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct Planning {
pub scheduled: Option<PlanningTimestamp>,
pub deadline: Option<PlanningTimestamp>,
pub closed: Option<PlanningTimestamp>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PlanningTimestamp {
pub date: String,
pub time: Option<String>,
pub end_time: Option<String>,
pub repeater: Option<String>,
pub warning: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub raw: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AgendaEntry {
pub schema: String,
pub item: Item,
pub occurrence: Occurrence,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Occurrence {
#[serde(rename = "type")]
pub kind: OccurrenceType,
pub date: String,
pub time: Option<String>,
#[serde(default)]
pub end_time: Option<String>,
pub extra: String,
#[serde(default)]
pub days: i64,
#[serde(default)]
pub late: bool,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum OccurrenceType {
Scheduled,
PastScheduled,
Deadline,
UpcomingDeadline,
Timestamp,
Block,
Closed,
Todo,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Mutation {
pub schema: String,
pub ok: bool,
pub item: Item,
pub diff: String,
pub journal_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub repeated: Option<Repeat>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Repeat {
pub done_state: String,
pub next_date: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ErrorResponse {
pub schema: String,
pub code: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub current: Option<Item>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ErrorCode {
CaptureTargetNotFound,
CommandFailed,
EditorConflict,
EncodeFailed,
FileNotFound,
IdConflict,
ImportFailed,
InvalidAgendaFile,
InvalidAgendaPattern,
InvalidArguments,
InvalidBatch,
InvalidBody,
InvalidCapture,
InvalidConfig,
InvalidDate,
InvalidEdit,
InvalidIdChange,
InvalidImport,
InvalidJournal,
InvalidJournalState,
InvalidMatch,
InvalidNote,
InvalidPatch,
InvalidPath,
InvalidPlan,
InvalidPriority,
InvalidPrompt,
InvalidProperty,
InvalidQuery,
InvalidRange,
InvalidRef,
InvalidRefile,
InvalidRepeater,
InvalidRoot,
InvalidSpan,
InvalidTag,
InvalidTemplate,
InvalidTitle,
InvalidTodo,
InvalidUtf8,
InvalidView,
IoFailed,
JournalConflict,
JournalFailed,
JournalNotFound,
LockFailed,
ParseFailed,
PathConflict,
RefConflict,
UndoConflict,
UnknownProfile,
UnknownTemplate,
UnknownView,
WalkFailed,
}
impl ErrorCode {
pub fn as_str(self) -> &'static str {
match self {
Self::CaptureTargetNotFound => "capture_target_not_found",
Self::CommandFailed => "command_failed",
Self::EditorConflict => "editor_conflict",
Self::EncodeFailed => "encode_failed",
Self::FileNotFound => "file_not_found",
Self::IdConflict => "id_conflict",
Self::ImportFailed => "import_failed",
Self::InvalidAgendaFile => "invalid_agenda_file",
Self::InvalidAgendaPattern => "invalid_agenda_pattern",
Self::InvalidArguments => "invalid_arguments",
Self::InvalidBatch => "invalid_batch",
Self::InvalidBody => "invalid_body",
Self::InvalidCapture => "invalid_capture",
Self::InvalidConfig => "invalid_config",
Self::InvalidDate => "invalid_date",
Self::InvalidEdit => "invalid_edit",
Self::InvalidIdChange => "invalid_id_change",
Self::InvalidImport => "invalid_import",
Self::InvalidJournal => "invalid_journal",
Self::InvalidJournalState => "invalid_journal_state",
Self::InvalidMatch => "invalid_match",
Self::InvalidNote => "invalid_note",
Self::InvalidPatch => "invalid_patch",
Self::InvalidPath => "invalid_path",
Self::InvalidPlan => "invalid_plan",
Self::InvalidPriority => "invalid_priority",
Self::InvalidPrompt => "invalid_prompt",
Self::InvalidProperty => "invalid_property",
Self::InvalidQuery => "invalid_query",
Self::InvalidRange => "invalid_range",
Self::InvalidRef => "invalid_ref",
Self::InvalidRefile => "invalid_refile",
Self::InvalidRepeater => "invalid_repeater",
Self::InvalidRoot => "invalid_root",
Self::InvalidSpan => "invalid_span",
Self::InvalidTag => "invalid_tag",
Self::InvalidTemplate => "invalid_template",
Self::InvalidTitle => "invalid_title",
Self::InvalidTodo => "invalid_todo",
Self::InvalidUtf8 => "invalid_utf8",
Self::InvalidView => "invalid_view",
Self::IoFailed => "io_failed",
Self::JournalConflict => "journal_conflict",
Self::JournalFailed => "journal_failed",
Self::JournalNotFound => "journal_not_found",
Self::LockFailed => "lock_failed",
Self::ParseFailed => "parse_failed",
Self::PathConflict => "path_conflict",
Self::RefConflict => "ref_conflict",
Self::UndoConflict => "undo_conflict",
Self::UnknownProfile => "unknown_profile",
Self::UnknownTemplate => "unknown_template",
Self::UnknownView => "unknown_view",
Self::WalkFailed => "walk_failed",
}
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Debug)]
pub struct AgdError {
code: ErrorCode,
response: Box<ErrorResponse>,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
}
impl AgdError {
pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
Self {
code,
response: Box::new(ErrorResponse {
schema: ERROR_SCHEMA.to_string(),
code: code.as_str().to_string(),
message: message.into(),
current: None,
}),
source: None,
}
}
pub fn with_context(mut self, context: &str) -> Self {
self.response.message = format!("{context}: {}", self.response.message);
self
}
pub fn with_current(mut self, current: Item) -> Self {
self.response.current = Some(current);
self
}
pub fn with_source(
code: ErrorCode,
message: impl Into<String>,
source: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self {
source: Some(Box::new(source)),
..Self::new(code, message)
}
}
pub fn code(&self) -> ErrorCode {
self.code
}
pub fn response(&self) -> &ErrorResponse {
&self.response
}
pub fn into_response(self) -> ErrorResponse {
*self.response
}
}
impl fmt::Display for AgdError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.response.message)
}
}
impl std::error::Error for AgdError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source
.as_deref()
.map(|source| source as &(dyn std::error::Error + 'static))
}
}
impl From<std::io::Error> for AgdError {
fn from(error: std::io::Error) -> Self {
Self::with_source(ErrorCode::IoFailed, error.to_string(), error)
}
}