use crate::error::{IoError, IoErrorKind, KrikResult, MarkdownError, MarkdownErrorKind};
use std::path::Path;
use tracing::warn;
pub struct ErrorRecovery;
impl ErrorRecovery {
pub fn recover_markdown_error(
error: &MarkdownError,
file_path: &Path,
content: &str,
) -> Option<(crate::parser::FrontMatter, String)> {
match &error.kind {
MarkdownErrorKind::InvalidFrontMatter(_) => {
warn!(
"Warning: Invalid front matter in {}, using content without metadata",
file_path.display()
);
if let Some(stripped) = content.strip_prefix("---\n") {
if let Some(end_pos) = stripped.find("\n---\n") {
let markdown_content = &stripped[end_pos + 5..];
return Some((
Self::create_default_frontmatter(),
markdown_content.to_string(),
));
}
}
Some((Self::create_default_frontmatter(), content.to_string()))
}
MarkdownErrorKind::InvalidDate(_) => {
warn!(
"Warning: Invalid date in {}, using current date",
file_path.display()
);
Some((Self::create_default_frontmatter(), content.to_string()))
}
_ => None, }
}
pub fn recover_io_error(error: &IoError) -> Option<String> {
match &error.kind {
IoErrorKind::NotFound => {
Some(format!(
"File not found: {}\n Suggestions:\n - Check if the path is correct\n - Ensure the file exists\n - Check file permissions",
error.path.display()
))
}
IoErrorKind::PermissionDenied => {
Some(format!(
"Permission denied: {}\n Suggestions:\n - Check file/directory permissions\n - Run with appropriate user privileges\n - Check if file is locked by another process",
error.path.display()
))
}
IoErrorKind::InvalidPath => {
Some(format!(
"Invalid path: {}\n Suggestions:\n - Check for invalid characters in path\n - Ensure path length is within limits\n - Use forward slashes (/) for path separators",
error.path.display()
))
}
_ => None,
}
}
fn create_default_frontmatter() -> crate::parser::FrontMatter {
use chrono::Utc;
use std::collections::HashMap;
crate::parser::FrontMatter {
title: Some("Untitled".to_string()),
date: Some(Utc::now()),
tags: None,
lang: Some("en".to_string()),
draft: Some(false),
pdf: None,
extra: HashMap::new(),
}
}
pub fn with_error_tolerance<F, T>(
operation: F,
description: &str,
continue_on_error: bool,
) -> KrikResult<Vec<T>>
where
F: Fn() -> KrikResult<Vec<T>>,
{
match operation() {
Ok(results) => Ok(results),
Err(e) if continue_on_error => {
warn!("Warning: {description} failed with error: {e}");
warn!("Continuing with partial results...");
Ok(Vec::new()) }
Err(e) => Err(e),
}
}
pub fn validate_project_structure(content_dir: &Path) -> Vec<String> {
let mut suggestions = Vec::new();
if !content_dir.exists() {
suggestions.push(format!(
"Content directory '{}' does not exist. Create it with: mkdir -p {}",
content_dir.display(),
content_dir.display()
));
return suggestions;
}
let posts_dir = content_dir.join("posts");
let pages_dir = content_dir.join("pages");
let site_config = content_dir.join("site.toml");
if !posts_dir.exists() {
suggestions.push(format!(
"Consider creating a 'posts' directory: mkdir -p {}",
posts_dir.display()
));
}
if !pages_dir.exists() {
suggestions.push(format!(
"Consider creating a 'pages' directory: mkdir -p {}",
pages_dir.display()
));
}
if !site_config.exists() {
suggestions.push(format!(
"Consider creating a site configuration: echo 'title = \"My Site\"' > {}",
site_config.display()
));
}
let has_markdown = walkdir::WalkDir::new(content_dir)
.follow_links(true)
.into_iter()
.filter_map(|e| e.ok())
.any(|entry| {
entry.path().is_file() && entry.path().extension().is_some_and(|ext| ext == "md")
});
if !has_markdown {
suggestions.push(
"No markdown files found. Create your first post with: kk post \"My First Post\""
.to_string(),
);
}
suggestions
}
pub fn auto_fix_issues(content_dir: &Path) -> KrikResult<Vec<String>> {
let mut fixes_applied = Vec::new();
let dirs_to_create = ["posts", "pages"];
for dir_name in &dirs_to_create {
let dir_path = content_dir.join(dir_name);
if !dir_path.exists() {
std::fs::create_dir_all(&dir_path).map_err(|e| {
crate::io_error!(
IoErrorKind::WriteFailed(e),
&dir_path,
"Creating directory structure"
)
})?;
fixes_applied.push(format!("Created directory: {}", dir_path.display()));
}
}
let site_config = content_dir.join("site.toml");
if !site_config.exists() {
let default_config = r#"title = "My Krik Site"
# base_url = "https://example.com" # Uncomment and update for production
"#;
std::fs::write(&site_config, default_config).map_err(|e| {
crate::io_error!(
IoErrorKind::WriteFailed(e),
&site_config,
"Creating default site configuration"
)
})?;
fixes_applied.push(format!(
"Created default site.toml: {}",
site_config.display()
));
}
Ok(fixes_applied)
}
}
pub trait ErrorRecoverable<T> {
fn continue_on_error(self, description: &str) -> KrikResult<Option<T>>;
fn or_default_with_warning(self, default: T, description: &str) -> KrikResult<T>;
}
impl<T> ErrorRecoverable<T> for KrikResult<T> {
fn continue_on_error(self, description: &str) -> KrikResult<Option<T>> {
match self {
Ok(value) => Ok(Some(value)),
Err(e) => {
warn!("Warning: {} failed: {}", description, e);
warn!("Continuing...");
Ok(None)
}
}
}
fn or_default_with_warning(self, default: T, description: &str) -> KrikResult<T> {
match self {
Ok(value) => Ok(value),
Err(e) => {
warn!("Warning: {} failed: {}", description, e);
warn!("Using default value");
Ok(default)
}
}
}
}