alloc optimization and enum refactors

This commit is contained in:
slonkazoid 2024-12-16 21:16:45 +03:00
parent ed81dcd223
commit bed8ae7849
Signed by: slonk
SSH key fingerprint: SHA256:tbZfJX4IOvZ0LGWOWu5Ijo8jfMPi78TU7x1VoEeCIjM
7 changed files with 212 additions and 179 deletions

View file

@ -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"

View file

@ -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<PostMetadata>,
rss: bool,
js: bool,
tags: Map<String, serde_json::Value>,
tags: IndexMap<Arc<str>, 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<str>,
rendered_in: RenderStats,
js: bool,
color: Option<&'a str>,
joined_tags: String,
style: &'a StyleConfig,
raw_name: Option<&'a str>,
raw_name: Option<String>,
}
#[derive(Deserialize)]
@ -84,12 +82,12 @@ struct QueryParams {
other: IndexMap<String, Value>,
}
fn collect_tags(posts: &Vec<PostMetadata>) -> Map<String, serde_json::Value> {
let mut tags = HashMap::new();
fn collect_tags(posts: &Vec<PostMetadata>) -> IndexMap<Arc<str>, 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<PostMetadata>) -> Map<String, serde_json::Value> {
}
}
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<String, serde_json::Value>, delim: &str) -> String {
fn join_tags_for_meta(tags: &IndexMap<Arc<str>, 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::<Vec<Category>>(),
)
.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<AppState>,
Path(name): Path<String>,
Path(name): Path<Arc<str>>,
Query(query): Query<QueryParams>,
) -> AppResult<impl IntoResponse> {
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()),
}
}

View file

@ -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<str>),
}
impl From<fronma::error::Error> for PostError {

View file

@ -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<String>,
pub icon_alt: Option<String>,
pub color: Option<String>,
pub title: Arc<str>,
pub description: Arc<str>,
pub author: Arc<str>,
pub icon: Option<Arc<str>>,
pub icon_alt: Option<Arc<str>>,
pub color: Option<Arc<str>>,
pub created_at: Option<DateTime<Utc>>,
pub modified_at: Option<DateTime<Utc>>,
#[serde(default)]
pub tags: BTreeSet<String>,
pub tags: BTreeSet<Arc<str>>,
pub dont_cache: bool,
}
impl BlagMetadata {
pub fn into_full(self, name: String) -> (PostMetadata, bool) {
pub fn into_full(self, name: Arc<str>) -> (PostMetadata, bool) {
(
PostMetadata {
name,
@ -79,7 +79,7 @@ impl Blag {
async fn render(
&self,
name: &str,
name: Arc<str>,
path: impl AsRef<Path>,
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<String, Value>,
) -> Result<Vec<(PostMetadata, String, RenderStats)>, PostError> {
) -> Result<Vec<(PostMetadata, Arc<str>, 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<str>,
query: &IndexMap<String, Value>,
) -> Result<ReturnedPost, PostError> {
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<Option<String>, PostError> {
fn is_raw(&self, name: &str) -> bool {
name.ends_with(".sh")
}
fn as_raw(&self, name: &str) -> Option<String> {
let mut buf = String::with_capacity(name.len() + 3);
buf += name;
buf += ".sh";
Ok(Some(buf))
Some(buf)
}
}

View file

@ -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<str>,
pub mtime: u64,
pub extra: u64,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct FileCache(HashMap<String, CacheValue>, u16);
pub struct FileCache(HashMap<Arc<str>, 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<str>,
metadata: PostMetadata,
mtime: u64,
rendered: String,
rendered: Arc<str>,
extra: u64,
) -> Result<(), (String, (PostMetadata, String))> {
) -> Result<(), (Arc<str>, (PostMetadata, Arc<str>))> {
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<str>, CacheValue)> {
self.0.remove_async(name).await
}

View file

@ -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<String>,
pub icon_alt: Option<String>,
pub color: Option<String>,
pub title: Arc<str>,
pub description: Arc<str>,
pub author: Arc<str>,
pub icon: Option<Arc<str>>,
pub icon_alt: Option<Arc<str>>,
pub color: Option<Arc<str>>,
pub created_at: Option<DateTime<Utc>>,
pub modified_at: Option<DateTime<Utc>>,
#[serde(default)]
pub tags: BTreeSet<String>,
pub tags: BTreeSet<Arc<str>>,
}
impl FrontMatter {
pub fn into_full(
self,
name: String,
name: Arc<str>,
created: Option<SystemTime>,
modified: Option<SystemTime>,
) -> PostMetadata {
@ -94,9 +94,9 @@ impl MarkdownPosts {
async fn parse_and_render(
&self,
name: String,
name: Arc<str>,
path: impl AsRef<Path>,
) -> Result<(PostMetadata, String, (Duration, Duration)), PostError> {
) -> Result<(PostMetadata, Arc<str>, (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<String, Value>,
) -> Result<Vec<(PostMetadata, String, RenderStats)>, PostError> {
) -> Result<Vec<(PostMetadata, Arc<str>, 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<str> =
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<str>,
_query: &IndexMap<String, Value>,
) -> Result<ReturnedPost, PostError> {
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<Option<String>, PostError> {
fn is_raw(&self, name: &str) -> bool {
name.ends_with(".md")
}
fn as_raw(&self, name: &str) -> Option<String> {
let mut buf = String::with_capacity(name.len() + 3);
buf += name;
buf += ".md";
Ok(Some(buf))
Some(buf)
}
}

View file

@ -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<str>
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PostMetadata {
pub name: String,
pub title: String,
pub description: String,
pub author: String,
pub icon: Option<String>,
pub icon_alt: Option<String>,
pub color: Option<String>,
pub name: Arc<str>,
pub title: Arc<str>,
pub description: Arc<str>,
pub author: Arc<str>,
pub icon: Option<Arc<str>>,
pub icon_alt: Option<Arc<str>>,
pub color: Option<Arc<str>>,
pub created_at: Option<DateTime<Utc>>,
pub modified_at: Option<DateTime<Utc>>,
pub tags: Vec<String>,
pub tags: Vec<Arc<str>>,
}
#[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<str>,
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<u8>, HeaderValue),
Rendered {
meta: PostMetadata,
body: Arc<str>,
perf: RenderStats,
},
Raw {
buffer: Vec<u8>,
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<String, Value>,
) -> Result<Vec<(PostMetadata, String, RenderStats)>, PostError>;
) -> Result<Vec<(PostMetadata, Arc<str>, 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<str>,
query: &IndexMap<String, Value>,
) -> Result<PostMetadata, PostError> {
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<str>,
query: &IndexMap<String, Value>,
) -> Result<ReturnedPost, PostError>;
async fn cleanup(&self) {}
#[allow(unused)]
async fn as_raw(&self, name: &str) -> Result<Option<String>, PostError> {
Ok(None)
fn is_raw(&self, name: &str) -> bool {
false
}
#[allow(unused)]
fn as_raw(&self, name: &str) -> Option<String> {
None
}
}