mdvault_core/domain/traits.rs
1//! Domain traits for note type behaviors.
2//!
3//! These traits define the polymorphic interface for first-class note types,
4//! allowing type-specific behavior without scattered if/else checks.
5
6use std::path::PathBuf;
7
8use super::context::{CreationContext, FieldPrompt, PromptContext};
9
10/// Errors that can occur during domain operations.
11#[derive(Debug, thiserror::Error)]
12pub enum DomainError {
13 #[error("ID generation failed: {0}")]
14 IdGeneration(String),
15
16 #[error("path resolution failed: {0}")]
17 PathResolution(String),
18
19 #[error("lifecycle hook failed: {0}")]
20 LifecycleHook(String),
21
22 #[error("validation failed: {0}")]
23 Validation(String),
24
25 #[error("IO error: {0}")]
26 Io(#[from] std::io::Error),
27
28 #[error("missing required field: {0}")]
29 MissingField(String),
30
31 #[error("{0}")]
32 Other(String),
33}
34
35pub type DomainResult<T> = Result<T, DomainError>;
36
37/// How does this note type generate identifiers and paths?
38pub trait NoteIdentity {
39 /// Generate a unique ID for this note type.
40 /// Returns None if this type doesn't use IDs (e.g., daily notes use dates).
41 fn generate_id(&self, ctx: &CreationContext) -> DomainResult<Option<String>>;
42
43 /// Determine the output path for this note.
44 /// This is called after prompts are collected and before_create runs.
45 fn output_path(&self, ctx: &CreationContext) -> DomainResult<PathBuf>;
46
47 /// Get the fields that this type manages (core metadata).
48 /// These fields will be preserved through template/hook modifications.
49 fn core_fields(&self) -> Vec<&'static str>;
50}
51
52/// What happens during the note lifecycle?
53pub trait NoteLifecycle {
54 /// Called before the note is written to disk.
55 /// Can modify the context (e.g., set computed vars, update counters).
56 fn before_create(&self, ctx: &mut CreationContext) -> DomainResult<()>;
57
58 /// Called after the note is successfully written to disk.
59 /// Used for side effects (logging to daily, reindexing, etc.).
60 fn after_create(&self, ctx: &CreationContext, content: &str) -> DomainResult<()>;
61}
62
63/// What interactive prompts does this type need?
64pub trait NotePrompts {
65 /// Return type-specific prompts (e.g., project selector for tasks).
66 /// These run BEFORE schema-based prompts.
67 fn type_prompts(&self, ctx: &PromptContext) -> Vec<FieldPrompt>;
68
69 /// Whether this type should prompt for schema fields.
70 /// Default: true. Override to false for types that compute all fields.
71 fn should_prompt_schema(&self) -> bool {
72 true
73 }
74}
75
76/// Combined trait for a note type behavior.
77/// All first-class types implement this.
78pub trait NoteBehavior: NoteIdentity + NoteLifecycle + NotePrompts + Send + Sync {
79 /// Get the type name for this behavior.
80 fn type_name(&self) -> &'static str;
81}