use crate::{
common::timestamp,
traits::{HasIdPath, HashId, Validatable},
APP_PATH, PUBLIC_PATH,
};
use serde::{Deserialize, Serialize};
use url::Url;
#[cfg(target_arch = "wasm32")]
use crate::traits::Json;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(feature = "openapi")]
use utoipa::ToSchema;
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
#[derive(Serialize, Deserialize, Default, Clone, Debug)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct PubkyAppBookmark {
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(skip))]
pub uri: String,
pub created_at: i64,
}
impl PubkyAppBookmark {
pub fn new(uri: String) -> Self {
let created_at = timestamp();
Self { uri, created_at }.sanitize()
}
}
#[cfg(target_arch = "wasm32")]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
impl PubkyAppBookmark {
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = fromJson))]
pub fn from_json(js_value: &JsValue) -> Result<Self, String> {
Self::import_json(js_value)
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = toJson))]
pub fn to_json(&self) -> Result<JsValue, String> {
self.export_json()
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter))]
pub fn uri(&self) -> String {
self.uri.clone()
}
}
#[cfg(target_arch = "wasm32")]
impl Json for PubkyAppBookmark {}
impl HashId for PubkyAppBookmark {
fn get_id_data(&self) -> String {
self.uri.clone()
}
}
impl HasIdPath for PubkyAppBookmark {
const PATH_SEGMENT: &'static str = "bookmarks/";
fn create_path(id: &str) -> String {
[PUBLIC_PATH, APP_PATH, Self::PATH_SEGMENT, id].concat()
}
}
impl Validatable for PubkyAppBookmark {
fn validate(&self, id: Option<&str>) -> Result<(), String> {
if let Some(id) = id {
self.validate_id(id)?;
}
Url::parse(&self.uri)
.map(|_| ())
.map_err(|_| format!("Validation Error: Invalid URI format: {}", self.uri))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{post_uri_builder, traits::Validatable};
#[test]
fn test_create_bookmark_id() {
let bookmark = PubkyAppBookmark {
uri: post_uri_builder("user_id".into(), "post_id".into()),
created_at: 1627849723,
};
let bookmark_id = bookmark.create_id();
assert_eq!(bookmark_id, "2GN0JCHX9NYXPECQDS8KSMSE7M");
}
#[test]
fn test_create_path() {
let post_uri = post_uri_builder("user_id".into(), "post_id".into());
let bookmark = PubkyAppBookmark {
uri: post_uri,
created_at: 1627849723,
};
let expected_id = bookmark.create_id();
let expected_path = format!("{}{}bookmarks/{}", PUBLIC_PATH, APP_PATH, expected_id);
let path = PubkyAppBookmark::create_path(&expected_id);
assert_eq!(path, expected_path);
}
#[test]
fn test_validate() {
let post_uri = post_uri_builder("user_id".into(), "post_id".into());
let bookmark = PubkyAppBookmark::new(post_uri);
let id = bookmark.create_id();
let result = bookmark.validate(Some(&id));
assert!(result.is_ok());
}
#[test]
fn test_validate_invalid_id() {
let post_uri = post_uri_builder("user_id".into(), "post_id".into());
let bookmark = PubkyAppBookmark::new(post_uri);
let invalid_id = "INVALIDID";
let result = bookmark.validate(Some(invalid_id));
assert!(result.is_err());
}
#[test]
fn test_validate_invalid_uri() {
let post_uri = "user_id/pub/pubky.app/posts/post_id".to_string();
let bookmark = PubkyAppBookmark::new(post_uri);
let id = bookmark.create_id();
let res = bookmark.validate(Some(&id));
assert!(res
.unwrap_err()
.starts_with("Validation Error: Invalid URI format"));
}
#[test]
fn test_try_from_valid() {
let bookmark_json = r#"
{
"uri": "pubky://user_id/pub/pubky.app/posts/post_id",
"created_at": 1627849723
}
"#;
let uri = post_uri_builder("user_id".into(), "post_id".into());
let bookmark = PubkyAppBookmark::new(uri.clone());
let id = bookmark.create_id();
let blob = bookmark_json.as_bytes();
let bookmark_parsed = <PubkyAppBookmark as Validatable>::try_from(blob, &id).unwrap();
assert_eq!(bookmark_parsed.uri, uri);
}
}