diff --git a/Cargo.toml b/Cargo.toml index 96785a3..dfce33c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ mime_guess = "2.0.5" notify-debouncer-full = { version = "0.3.1", default-features = false } rss = "2.0.7" scc = { version = "2.1.0", features = ["serde"] } -serde = { version = "1.0.197", features = ["derive"] } +serde = { version = "1.0.197", features = ["derive", "rc"] } serde-value = "0.7.0" serde_json = { version = "1.0.124", features = ["preserve_order"] } syntect = "5.2.0" diff --git a/src/app.rs b/src/app.rs index 34e3ae5..5b138ed 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; @@ -13,7 +12,6 @@ use include_dir::{include_dir, Dir}; use indexmap::IndexMap; use rss::{Category, ChannelBuilder, ItemBuilder}; use serde::{Deserialize, Serialize}; -use serde_json::Map; use serde_value::Value; use tokio::sync::RwLock; use tower::service_fn; @@ -57,7 +55,7 @@ struct IndexTemplate<'a> { posts: Vec, rss: bool, js: bool, - tags: Map, + tags: IndexMap, u64>, joined_tags: String, style: &'a StyleConfig, } @@ -66,13 +64,13 @@ struct IndexTemplate<'a> { struct PostTemplate<'a> { bingus_info: &'a BingusInfo, meta: &'a PostMetadata, - rendered: String, + rendered: Arc, rendered_in: RenderStats, js: bool, color: Option<&'a str>, joined_tags: String, style: &'a StyleConfig, - raw_name: Option<&'a str>, + raw_name: Option, } #[derive(Deserialize)] @@ -84,12 +82,12 @@ struct QueryParams { other: IndexMap, } -fn collect_tags(posts: &Vec) -> Map { - let mut tags = HashMap::new(); +fn collect_tags(posts: &Vec) -> IndexMap, u64> { + let mut tags = IndexMap::new(); for post in posts { for tag in &post.tags { - if let Some((existing_tag, count)) = tags.remove_entry(tag) { + if let Some((existing_tag, count)) = tags.swap_remove_entry(tag) { tags.insert(existing_tag, count + 1); } else { tags.insert(tag.clone(), 1); @@ -97,21 +95,13 @@ fn collect_tags(posts: &Vec) -> Map { } } - let mut tags: Vec<(String, u64)> = tags.into_iter().collect(); + tags.sort_unstable_by(|k1, _v1, k2, _v2| k1.cmp(k2)); + tags.sort_by(|_k1, v1, _k2, v2| v1.cmp(v2)); - tags.sort_unstable_by_key(|(v, _)| v.clone()); - tags.sort_by_key(|(_, v)| -(*v as i64)); - - let mut map = Map::new(); - - for tag in tags.into_iter() { - map.insert(tag.0, tag.1.into()); - } - - map + tags } -fn join_tags_for_meta(tags: &Map, delim: &str) -> String { +fn join_tags_for_meta(tags: &IndexMap, u64>, delim: &str) -> String { let mut s = String::new(); let tags = tags.keys().enumerate(); let len = tags.len(); @@ -207,21 +197,21 @@ async fn rss( for (metadata, content, _) in posts { channel.item( ItemBuilder::default() - .title(metadata.title) - .description(metadata.description) - .author(metadata.author) + .title(metadata.title.to_string()) + .description(metadata.description.to_string()) + .author(metadata.author.to_string()) .categories( metadata .tags .into_iter() .map(|tag| Category { - name: tag, + name: tag.to_string(), domain: None, }) .collect::>(), ) .pub_date(metadata.created_at.map(|date| date.to_rfc2822())) - .content(content) + .content(content.to_string()) .link( config .rss @@ -246,15 +236,18 @@ async fn post( templates: reg, .. }): State, - Path(name): Path, + Path(name): Path>, Query(query): Query, ) -> AppResult { - match posts.get_post(&name, &query.other).await? { - ReturnedPost::Rendered(ref meta, rendered, rendered_in) => { + match posts.get_post(name.clone(), &query.other).await? { + ReturnedPost::Rendered { + ref meta, + body: rendered, + perf: rendered_in, + } => { let joined_tags = meta.tags.join(", "); let reg = reg.read().await; - let raw_name; let rendered = reg.render( "post", &PostTemplate { @@ -269,20 +262,19 @@ async fn post( .or(config.style.default_color.as_deref()), joined_tags, style: &config.style, - raw_name: if config.markdown_access { - raw_name = posts.as_raw(&meta.name).await?; - raw_name.as_deref() - } else { - None - }, + raw_name: config + .markdown_access + .then(|| posts.as_raw(&meta.name)) + .unwrap_or(None), }, ); drop(reg); Ok(Html(rendered?).into_response()) } - ReturnedPost::Raw(body, content_type) => { - Ok(([(CONTENT_TYPE, content_type)], body).into_response()) - } + ReturnedPost::Raw { + buffer, + content_type, + } => Ok(([(CONTENT_TYPE, content_type)], buffer).into_response()), } } diff --git a/src/error.rs b/src/error.rs index 38be3c2..93d96b2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use askama_axum::Template; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; @@ -14,7 +16,7 @@ pub enum PostError { #[error("failed to render post: {0}")] RenderError(String), #[error("post {0:?} not found")] - NotFound(String), + NotFound(Arc), } impl From for PostError { diff --git a/src/post/blag.rs b/src/post/blag.rs index e1923f6..5dead2a 100644 --- a/src/post/blag.rs +++ b/src/post/blag.rs @@ -27,21 +27,21 @@ use super::{ApplyFilters, PostManager, PostMetadata, RenderStats, ReturnedPost}; #[derive(Deserialize, Debug)] struct BlagMetadata { - pub title: String, - pub description: String, - pub author: String, - pub icon: Option, - pub icon_alt: Option, - pub color: Option, + pub title: Arc, + pub description: Arc, + pub author: Arc, + pub icon: Option>, + pub icon_alt: Option>, + pub color: Option>, pub created_at: Option>, pub modified_at: Option>, #[serde(default)] - pub tags: BTreeSet, + pub tags: BTreeSet>, pub dont_cache: bool, } impl BlagMetadata { - pub fn into_full(self, name: String) -> (PostMetadata, bool) { + pub fn into_full(self, name: Arc) -> (PostMetadata, bool) { ( PostMetadata { name, @@ -79,7 +79,7 @@ impl Blag { async fn render( &self, - name: &str, + name: Arc, path: impl AsRef, query_json: String, ) -> Result<(PostMetadata, String, (Duration, Duration), bool), PostError> { @@ -105,7 +105,7 @@ impl Blag { let blag_meta: BlagMetadata = serde_json::from_str(&buf)?; debug!("blag meta: {blag_meta:?}"); - let (meta, dont_cache) = blag_meta.into_full(name.to_string()); + let (meta, dont_cache) = blag_meta.into_full(name); let parsed = start.elapsed(); let rendering = Instant::now(); @@ -132,7 +132,7 @@ impl PostManager for Blag { &self, filters: &[Filter<'_>], query: &IndexMap, - ) -> Result, PostError> { + ) -> Result, RenderStats)>, PostError> { let mut set = FuturesUnordered::new(); let mut posts = Vec::new(); let mut files = tokio::fs::read_dir(&self.root).await?; @@ -149,17 +149,16 @@ impl PostManager for Blag { let file_type = entry.file_type().await?; if file_type.is_file() { - let name = match entry.file_name().into_string() { + let mut name = match entry.file_name().into_string() { Ok(v) => v, Err(_) => { continue; } }; - if name.ends_with(".sh") { - set.push( - async move { self.get_post(name.trim_end_matches(".sh"), query).await }, - ); + if self.is_raw(&name) { + name.truncate(3); + set.push(self.get_post(name.into(), query)); } } } @@ -167,8 +166,8 @@ impl PostManager for Blag { while let Some(result) = set.next().await { let post = match result { Ok(v) => match v { - ReturnedPost::Rendered(meta, content, stats) => (meta, content, stats), - ReturnedPost::Raw(..) => unreachable!(), + ReturnedPost::Rendered { meta, body, perf } => (meta, body, perf), + ReturnedPost::Raw { .. } => unreachable!(), }, Err(err) => { error!("error while rendering blagpost: {err}"); @@ -189,29 +188,29 @@ impl PostManager for Blag { #[instrument(level = "info", skip(self))] async fn get_post( &self, - name: &str, + name: Arc, query: &IndexMap, ) -> Result { let start = Instant::now(); - let mut path = self.root.join(name); + let mut path = self.root.join(&*name); - if name.ends_with(".sh") { - let mut buf = Vec::new(); + if self.is_raw(&name) { + let mut buffer = Vec::new(); let mut file = OpenOptions::new() .read(true) .open(&path) .await .map_err(|err| match err.kind() { - std::io::ErrorKind::NotFound => PostError::NotFound(name.to_string()), + std::io::ErrorKind::NotFound => PostError::NotFound(name), _ => PostError::IoError(err), })?; - file.read_to_end(&mut buf).await?; + file.read_to_end(&mut buffer).await?; - return Ok(ReturnedPost::Raw( - buf, - HeaderValue::from_static("text/x-shellscript"), - )); + return Ok(ReturnedPost::Raw { + buffer, + content_type: HeaderValue::from_static("text/x-shellscript"), + }); } else { path.add_extension("sh"); } @@ -219,12 +218,12 @@ impl PostManager for Blag { let stat = tokio::fs::metadata(&path) .await .map_err(|err| match err.kind() { - std::io::ErrorKind::NotFound => PostError::NotFound(name.to_string()), + std::io::ErrorKind::NotFound => PostError::NotFound(name.clone()), _ => PostError::IoError(err), })?; if !stat.is_file() { - return Err(PostError::NotFound(name.to_string())); + return Err(PostError::NotFound(name)); } let mtime = as_secs(&stat.modified()?); @@ -235,67 +234,69 @@ impl PostManager for Blag { let query_hash = hasher.finish(); let post = if let Some(cache) = &self.cache { - if let Some(CacheValue { - metadata, rendered, .. - }) = cache.lookup(name, mtime, query_hash).await + if let Some(CacheValue { meta, body, .. }) = + cache.lookup(&name, mtime, query_hash).await { - ReturnedPost::Rendered(metadata, rendered, RenderStats::Cached(start.elapsed())) + ReturnedPost::Rendered { + meta, + body, + perf: RenderStats::Cached(start.elapsed()), + } } else { let (meta, content, (parsed, rendered), dont_cache) = - self.render(name, path, query_json).await?; + self.render(name.clone(), path, query_json).await?; + let body = content.into(); if !dont_cache { cache - .insert( - name.to_string(), - meta.clone(), - mtime, - content.clone(), - query_hash, - ) + .insert(name, meta.clone(), mtime, Arc::clone(&body), query_hash) .await .unwrap_or_else(|err| warn!("failed to insert {:?} into cache", err.0)); } let total = start.elapsed(); - ReturnedPost::Rendered( + ReturnedPost::Rendered { meta, - content, - RenderStats::Rendered { + body, + perf: RenderStats::Rendered { total, parsed, rendered, }, - ) + } } } else { let (meta, content, (parsed, rendered), ..) = self.render(name, path, query_json).await?; let total = start.elapsed(); - ReturnedPost::Rendered( + ReturnedPost::Rendered { meta, - content, - RenderStats::Rendered { + body: content.into(), + perf: RenderStats::Rendered { total, parsed, rendered, }, - ) + } }; - if let ReturnedPost::Rendered(.., stats) = &post { - info!("rendered blagpost in {:?}", stats); + if let ReturnedPost::Rendered { perf, .. } = &post { + info!("rendered blagpost in {:?}", perf); } Ok(post) } - async fn as_raw(&self, name: &str) -> Result, PostError> { + fn is_raw(&self, name: &str) -> bool { + name.ends_with(".sh") + } + + fn as_raw(&self, name: &str) -> Option { let mut buf = String::with_capacity(name.len() + 3); buf += name; buf += ".sh"; - Ok(Some(buf)) + Some(buf) } } diff --git a/src/post/cache.rs b/src/post/cache.rs index 400fc1a..17bd5ff 100644 --- a/src/post/cache.rs +++ b/src/post/cache.rs @@ -1,5 +1,6 @@ use std::io::{Read, Write}; use std::ops::Deref; +use std::sync::Arc; use crate::config::CacheConfig; use crate::post::PostMetadata; @@ -10,18 +11,18 @@ use tokio::io::AsyncReadExt; use tracing::{debug, info, instrument}; /// do not persist cache if this version number changed -pub const CACHE_VERSION: u16 = 2; +pub const CACHE_VERSION: u16 = 3; #[derive(Serialize, Deserialize, Clone)] pub struct CacheValue { - pub metadata: PostMetadata, - pub rendered: String, + pub meta: PostMetadata, + pub body: Arc, pub mtime: u64, pub extra: u64, } #[derive(Serialize, Deserialize, Clone)] -pub struct FileCache(HashMap, u16); +pub struct FileCache(HashMap, CacheValue>, u16); impl Default for FileCache { fn default() -> Self { @@ -50,7 +51,7 @@ impl FileCache { Some(entry) => { let cached = entry.get(); if mtime <= cached.mtime { - Some(cached.metadata.clone()) + Some(cached.meta.clone()) } else { let _ = entry.remove(); None @@ -62,15 +63,15 @@ impl FileCache { pub async fn insert( &self, - name: String, + name: Arc, metadata: PostMetadata, mtime: u64, - rendered: String, + rendered: Arc, extra: u64, - ) -> Result<(), (String, (PostMetadata, String))> { + ) -> Result<(), (Arc, (PostMetadata, Arc))> { let value = CacheValue { - metadata, - rendered, + meta: metadata, + body: rendered, mtime, extra, }; @@ -84,13 +85,13 @@ impl FileCache { self.0 .insert_async(name, value) .await - .map_err(|x| (x.0, (x.1.metadata, x.1.rendered))) + .map_err(|x| (x.0, (x.1.meta, x.1.body))) } else { Ok(()) } } - pub async fn remove(&self, name: &str) -> Option<(String, CacheValue)> { + pub async fn remove(&self, name: &str) -> Option<(Arc, CacheValue)> { self.0.remove_async(name).await } diff --git a/src/post/markdown_posts.rs b/src/post/markdown_posts.rs index 616fe9c..adb44f9 100644 --- a/src/post/markdown_posts.rs +++ b/src/post/markdown_posts.rs @@ -24,29 +24,29 @@ use crate::config::Config; use crate::markdown_render::{build_syntect, render}; use crate::systemtime_as_secs::as_secs; -use super::cache::CacheGuard; +use super::cache::{CacheGuard, CacheValue}; use super::{ ApplyFilters, Filter, PostError, PostManager, PostMetadata, RenderStats, ReturnedPost, }; #[derive(Deserialize)] struct FrontMatter { - pub title: String, - pub description: String, - pub author: String, - pub icon: Option, - pub icon_alt: Option, - pub color: Option, + pub title: Arc, + pub description: Arc, + pub author: Arc, + pub icon: Option>, + pub icon_alt: Option>, + pub color: Option>, pub created_at: Option>, pub modified_at: Option>, #[serde(default)] - pub tags: BTreeSet, + pub tags: BTreeSet>, } impl FrontMatter { pub fn into_full( self, - name: String, + name: Arc, created: Option, modified: Option, ) -> PostMetadata { @@ -94,9 +94,9 @@ impl MarkdownPosts { async fn parse_and_render( &self, - name: String, + name: Arc, path: impl AsRef, - ) -> Result<(PostMetadata, String, (Duration, Duration)), PostError> { + ) -> Result<(PostMetadata, Arc, (Duration, Duration)), PostError> { let parsing_start = Instant::now(); let mut file = match tokio::fs::OpenOptions::new().read(true).open(&path).await { Ok(val) => val, @@ -117,16 +117,16 @@ impl MarkdownPosts { let parsing = parsing_start.elapsed(); let before_render = Instant::now(); - let post = render(body, Some(&self.syntect)); + let post = render(body, Some(&self.syntect)).into(); let rendering = before_render.elapsed(); if let Some(cache) = &self.cache { cache .insert( - name.to_string(), + name.clone(), metadata.clone(), as_secs(&modified), - post.clone(), + Arc::clone(&post), self.render_hash, ) .await @@ -143,7 +143,7 @@ impl PostManager for MarkdownPosts { &self, filters: &[Filter<'_>], query: &IndexMap, - ) -> Result, PostError> { + ) -> Result, RenderStats)>, PostError> { let mut posts = Vec::new(); let mut read_dir = fs::read_dir(&self.config.dirs.posts).await?; @@ -157,13 +157,14 @@ impl PostManager for MarkdownPosts { .file_stem() .unwrap() .to_string_lossy() - .to_string(); + .to_string() + .into(); - let post = self.get_post(&name, query).await?; - if let ReturnedPost::Rendered(meta, content, stats) = post + let post = self.get_post(Arc::clone(&name), query).await?; + if let ReturnedPost::Rendered { meta, body, perf } = post && meta.apply_filters(filters) { - posts.push((meta, content, stats)); + posts.push((meta, body, perf)); } } } @@ -185,7 +186,8 @@ impl PostManager for MarkdownPosts { if stat.is_file() && path.extension().is_some_and(|ext| ext == "md") { let mtime = as_secs(&stat.modified()?); - let name = String::from(path.file_stem().unwrap().to_string_lossy()); + let name: Arc = + String::from(path.file_stem().unwrap().to_string_lossy()).into(); if let Some(cache) = &self.cache && let Some(hit) = cache.lookup_metadata(&name, mtime).await @@ -218,42 +220,49 @@ impl PostManager for MarkdownPosts { #[instrument(level = "info", skip(self))] async fn get_post( &self, - name: &str, + name: Arc, _query: &IndexMap, ) -> Result { - let post = if self.config.markdown_access && name.ends_with(".md") { - let path = self.config.dirs.posts.join(name); + let post = if self.config.markdown_access && self.is_raw(&name) { + let path = self.config.dirs.posts.join(&*name); let mut file = match tokio::fs::OpenOptions::new().read(true).open(&path).await { Ok(value) => value, Err(err) => match err.kind() { io::ErrorKind::NotFound => { if let Some(cache) = &self.cache { - cache.remove(name).await; + cache.remove(&name).await; } - return Err(PostError::NotFound(name.to_string())); + return Err(PostError::NotFound(name)); } _ => return Err(PostError::IoError(err)), }, }; - let mut buf = Vec::with_capacity(4096); + let mut buffer = Vec::with_capacity(4096); - file.read_to_end(&mut buf).await?; + file.read_to_end(&mut buffer).await?; - ReturnedPost::Raw(buf, HeaderValue::from_static("text/plain")) + ReturnedPost::Raw { + buffer, + content_type: HeaderValue::from_static("text/plain"), + } } else { let start = Instant::now(); - let path = self.config.dirs.posts.join(name.to_owned() + ".md"); + let path = self + .config + .dirs + .posts + .join(self.as_raw(&name).unwrap_or_else(|| unreachable!())); let stat = match tokio::fs::metadata(&path).await { Ok(value) => value, Err(err) => match err.kind() { io::ErrorKind::NotFound => { if let Some(cache) = &self.cache { - cache.remove(name).await; + cache.remove(&name).await; } - return Err(PostError::NotFound(name.to_string())); + return Err(PostError::NotFound(name)); } _ => return Err(PostError::IoError(err)), }, @@ -261,30 +270,30 @@ impl PostManager for MarkdownPosts { let mtime = as_secs(&stat.modified()?); if let Some(cache) = &self.cache - && let Some(hit) = cache.lookup(name, mtime, self.render_hash).await + && let Some(CacheValue { meta, body, .. }) = + cache.lookup(&name, mtime, self.render_hash).await { - ReturnedPost::Rendered( - hit.metadata, - hit.rendered, - RenderStats::Cached(start.elapsed()), - ) + ReturnedPost::Rendered { + meta, + body, + perf: RenderStats::Cached(start.elapsed()), + } } else { - let (metadata, rendered, stats) = - self.parse_and_render(name.to_string(), path).await?; - ReturnedPost::Rendered( - metadata, - rendered, - RenderStats::Rendered { + let (meta, body, stats) = self.parse_and_render(name, path).await?; + ReturnedPost::Rendered { + meta, + body, + perf: RenderStats::Rendered { total: start.elapsed(), parsed: stats.0, rendered: stats.1, }, - ) + } } }; - if let ReturnedPost::Rendered(.., stats) = &post { - info!("rendered post in {:?}", stats); + if let ReturnedPost::Rendered { perf, .. } = &post { + info!("rendered post in {:?}", perf); } Ok(post) @@ -294,20 +303,29 @@ impl PostManager for MarkdownPosts { if let Some(cache) = &self.cache { cache .cleanup(|name| { - std::fs::metadata(self.config.dirs.posts.join(name.to_owned() + ".md")) - .ok() - .and_then(|metadata| metadata.modified().ok()) - .map(|mtime| as_secs(&mtime)) + std::fs::metadata( + self.config + .dirs + .posts + .join(self.as_raw(name).unwrap_or_else(|| unreachable!())), + ) + .ok() + .and_then(|metadata| metadata.modified().ok()) + .map(|mtime| as_secs(&mtime)) }) .await } } - async fn as_raw(&self, name: &str) -> Result, PostError> { + fn is_raw(&self, name: &str) -> bool { + name.ends_with(".md") + } + + fn as_raw(&self, name: &str) -> Option { let mut buf = String::with_capacity(name.len() + 3); buf += name; buf += ".md"; - Ok(Some(buf)) + Some(buf) } } diff --git a/src/post/mod.rs b/src/post/mod.rs index fc1541c..64503b6 100644 --- a/src/post/mod.rs +++ b/src/post/mod.rs @@ -2,9 +2,11 @@ pub mod blag; pub mod cache; pub mod markdown_posts; +use std::sync::Arc; use std::time::Duration; -use axum::{async_trait, http::HeaderValue}; +use axum::async_trait; +use axum::http::HeaderValue; use chrono::{DateTime, Utc}; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; @@ -17,19 +19,19 @@ pub use markdown_posts::MarkdownPosts; // TODO: replace String with Arc #[derive(Serialize, Deserialize, Clone, Debug)] pub struct PostMetadata { - pub name: String, - pub title: String, - pub description: String, - pub author: String, - pub icon: Option, - pub icon_alt: Option, - pub color: Option, + pub name: Arc, + pub title: Arc, + pub description: Arc, + pub author: Arc, + pub icon: Option>, + pub icon_alt: Option>, + pub color: Option>, pub created_at: Option>, pub modified_at: Option>, - pub tags: Vec, + pub tags: Vec>, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, Clone)] #[allow(unused)] pub enum RenderStats { Cached(Duration), @@ -39,13 +41,25 @@ pub enum RenderStats { rendered: Duration, }, Fetched(Duration), + Other { + verb: Arc, + time: Duration, + }, Unknown, } #[allow(clippy::large_enum_variant)] // Raw will be returned very rarely +#[derive(Debug, Clone)] pub enum ReturnedPost { - Rendered(PostMetadata, String, RenderStats), - Raw(Vec, HeaderValue), + Rendered { + meta: PostMetadata, + body: Arc, + perf: RenderStats, + }, + Raw { + buffer: Vec, + content_type: HeaderValue, + }, } pub enum Filter<'a> { @@ -57,7 +71,7 @@ impl Filter<'_> { match self { Filter::Tags(tags) => tags .iter() - .any(|tag| meta.tags.iter().any(|meta_tag| meta_tag == tag)), + .any(|tag| meta.tags.iter().any(|meta_tag| &**meta_tag == *tag)), } } } @@ -93,7 +107,7 @@ pub trait PostManager { &self, filters: &[Filter<'_>], query: &IndexMap, - ) -> Result, PostError>; + ) -> Result, RenderStats)>, PostError>; async fn get_max_n_post_metadata_with_optional_tag_sorted( &self, @@ -119,25 +133,30 @@ pub trait PostManager { #[allow(unused)] async fn get_post_metadata( &self, - name: &str, + name: Arc, query: &IndexMap, ) -> Result { - match self.get_post(name, query).await? { - ReturnedPost::Rendered(metadata, ..) => Ok(metadata), - ReturnedPost::Raw(..) => Err(PostError::NotFound(name.to_string())), + match self.get_post(name.clone(), query).await? { + ReturnedPost::Rendered { meta, .. } => Ok(meta), + ReturnedPost::Raw { .. } => Err(PostError::NotFound(name)), } } async fn get_post( &self, - name: &str, + name: Arc, query: &IndexMap, ) -> Result; async fn cleanup(&self) {} #[allow(unused)] - async fn as_raw(&self, name: &str) -> Result, PostError> { - Ok(None) + fn is_raw(&self, name: &str) -> bool { + false + } + + #[allow(unused)] + fn as_raw(&self, name: &str) -> Option { + None } }