From 7a580bd3a43d32e745b09b23cc99796a105773a7 Mon Sep 17 00:00:00 2001 From: SHAcollision Date: Mon, 3 Feb 2025 15:13:25 -0400 Subject: [PATCH 1/2] Add ffi to expose uri parser on wasm --- src/uri_parser.rs | 5 +-- src/wasm.rs | 110 +++++++++++++++++++++++++++++++++++++++------- tests/web.rs | 31 ++++++++++++- 3 files changed, 127 insertions(+), 19 deletions(-) diff --git a/src/uri_parser.rs b/src/uri_parser.rs index a9fa9c0..21bad63 100644 --- a/src/uri_parser.rs +++ b/src/uri_parser.rs @@ -1,6 +1,3 @@ -use std::fmt; -use url::Url; - use crate::{ traits::{HasPath, HasPubkyIdPath}, PubkyAppBlob, PubkyAppBookmark, PubkyAppFeed, PubkyAppFile, PubkyAppFollow, PubkyAppLastRead, @@ -8,6 +5,8 @@ use crate::{ PUBLIC_PATH, }; use std::convert::TryFrom; +use std::fmt; +use url::Url; #[derive(Debug, PartialEq, Default, Clone)] pub enum Resource { diff --git a/src/wasm.rs b/src/wasm.rs index e13a25b..68fbd9c 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -25,7 +25,7 @@ pub struct Meta { // Implement wasm_bindgen methods to expose read-only fields. #[wasm_bindgen] impl Meta { - // Getters clone the data out because String/JsValue is not Copy. + // Getters clone the data out because String is not Copy. #[wasm_bindgen(getter)] pub fn id(&self) -> String { self.id.clone() @@ -147,12 +147,12 @@ impl PubkySpecsBuilder { image: Option, links: JsValue, // a JS array of {title, url} or null status: Option, - ) -> Result { + ) -> Result { // 1) Convert JS 'links' -> Option> let links_vec: Option> = if links.is_null() || links.is_undefined() { None } else { - from_value(links)? + from_value(links).map_err(|e| e.to_string())? }; // 2) Build user domain object @@ -180,11 +180,11 @@ impl PubkySpecsBuilder { sort: String, content: Option, name: String, - ) -> Result { + ) -> Result { let tags_vec: Option> = if tags.is_null() || tags.is_undefined() { None } else { - from_value(tags)? + from_value(tags).map_err(|e| e.to_string())? }; // Use `FromStr` to parse enums @@ -219,7 +219,7 @@ impl PubkySpecsBuilder { src: String, content_type: String, size: i64, - ) -> Result { + ) -> Result { let file = PubkyAppFile::new(name, src, content_type, size); let file_id = file.create_id(); file.validate(&file_id)?; @@ -242,7 +242,7 @@ impl PubkySpecsBuilder { parent: Option, embed: Option, attachments: Option>, - ) -> Result { + ) -> Result { let post = PubkyAppPost::new(content, kind, parent, embed, attachments); let post_id = post.create_id(); post.validate(&post_id)?; @@ -258,7 +258,7 @@ impl PubkySpecsBuilder { // ----------------------------------------------------------------------------- #[wasm_bindgen(js_name = createTag)] - pub fn create_tag(&self, uri: String, label: String) -> Result { + pub fn create_tag(&self, uri: String, label: String) -> Result { let tag = PubkyAppTag::new(uri, label); let tag_id = tag.create_id(); tag.validate(&tag_id)?; @@ -274,7 +274,7 @@ impl PubkySpecsBuilder { // ----------------------------------------------------------------------------- #[wasm_bindgen(js_name = createBookmark)] - pub fn create_bookmark(&self, uri: String) -> Result { + pub fn create_bookmark(&self, uri: String) -> Result { let bookmark = PubkyAppBookmark::new(uri); let bookmark_id = bookmark.create_id(); bookmark.validate(&bookmark_id)?; @@ -290,7 +290,7 @@ impl PubkySpecsBuilder { // ----------------------------------------------------------------------------- #[wasm_bindgen(js_name = createFollow)] - pub fn create_follow(&self, followee_id: String) -> Result { + pub fn create_follow(&self, followee_id: String) -> Result { let follow = PubkyAppFollow::new(); follow.validate(&followee_id)?; // No ID in follow, so we pass user ID or empty @@ -306,7 +306,7 @@ impl PubkySpecsBuilder { // ----------------------------------------------------------------------------- #[wasm_bindgen(js_name = createMute)] - pub fn create_mute(&self, mutee_id: String) -> Result { + pub fn create_mute(&self, mutee_id: String) -> Result { let mute = PubkyAppMute::new(); mute.validate(&mutee_id)?; @@ -321,7 +321,7 @@ impl PubkySpecsBuilder { // ----------------------------------------------------------------------------- #[wasm_bindgen(js_name = createLastRead)] - pub fn create_last_read(&self) -> Result { + pub fn create_last_read(&self) -> Result { let last_read = PubkyAppLastRead::new(); last_read.validate("")?; @@ -336,10 +336,9 @@ impl PubkySpecsBuilder { // ----------------------------------------------------------------------------- #[wasm_bindgen(js_name = createBlob)] - pub fn create_blob(&self, blob_data: JsValue) -> Result { + pub fn create_blob(&self, blob_data: JsValue) -> Result { // Convert from JsValue (Uint8Array in JS) -> Vec in Rust - let data_vec: Vec = from_value(blob_data) - .map_err(|e| JsValue::from_str(&format!("Invalid blob bytes: {}", e)))?; + let data_vec: Vec = from_value(blob_data).map_err(|e| e.to_string())?; // Create the PubkyAppBlob let blob = PubkyAppBlob(data_vec); @@ -354,3 +353,84 @@ impl PubkySpecsBuilder { Ok(BlobResult { blob, meta }) } } + +/// This object represents the result of parsing a Pubky URI. It contains: +/// - `user_id`: the parsed user ID as a string. +/// - `resource`: a string representing the kind of resource (derived from internal `Resource` enum Display). +/// - `resource_id`: an optional resource identifier (if applicable). +#[wasm_bindgen] +pub struct ParsedUriResult { + #[wasm_bindgen(skip)] + user_id: String, + #[wasm_bindgen(skip)] + resource: String, + #[wasm_bindgen(skip)] + resource_id: Option, +} + +#[wasm_bindgen] +impl ParsedUriResult { + /// Returns the user ID. + #[wasm_bindgen(getter)] + pub fn user_id(&self) -> String { + self.user_id.clone() + } + + /// Returns the resource kind. + #[wasm_bindgen(getter)] + pub fn resource(&self) -> String { + self.resource.clone() + } + + /// Returns the resource ID if present. + #[wasm_bindgen(getter)] + pub fn resource_id(&self) -> Option { + self.resource_id.clone() + } +} + +/// Parses a Pubky URI and returns a strongly typed `ParsedUriResult`. +/// +/// This function wraps the internal ParsedUri ust parsing logic. It converts the result into a +/// strongly typed object that is easier to use in TypeScript. +/// +/// # Parameters +/// +/// - `uri`: A string slice representing the Pubky URI. The URI should follow the format: +/// `pubky:///pub/pubky.app/[/]`. +/// +/// # Returns +/// +/// On success, returns a `ParsedUriResult` with: +/// - `user_id`: the parsed user ID, +/// - `resource`: a string (derived from the Display implementation of internal `Resource` enum), +/// - `resource_id`: an optional resource identifier (if applicable). +/// +/// On failure, returns a JavaScript error (`String`) containing an error message. +/// +/// # Example (TypeScript) +/// +/// ```typescript +/// import { parse_uri } from "pubky-app-specs"; +/// +/// try { +/// const result = parse_uri("pubky://user123/pub/pubky.app/posts/abc123"); +/// console.log(result.user_id); // e.g. "user123" +/// console.log(result.resource); // e.g. "posts" +/// console.log(result.resource_id); // e.g. "abc123" or null +/// } catch (error) { +/// console.error("Error parsing URI:", error); +/// } +/// ``` +#[wasm_bindgen] +pub fn parse_uri(uri: &str) -> Result { + // Attempt to parse the URI using ParsedUri logic. + let parsed = ParsedUri::try_from(uri)?; + + // Build and return the strongly typed result. + Ok(ParsedUriResult { + user_id: parsed.user_id.to_string(), + resource: parsed.resource.to_string(), + resource_id: parsed.resource.id(), + }) +} diff --git a/tests/web.rs b/tests/web.rs index dd2933b..805b4cb 100644 --- a/tests/web.rs +++ b/tests/web.rs @@ -2,7 +2,7 @@ extern crate wasm_bindgen_test; use js_sys::Array; -use pubky_app_specs::{PubkyAppUserLink, PubkySpecsBuilder}; +use pubky_app_specs::{parse_uri, PubkyAppUserLink, PubkySpecsBuilder}; use serde_wasm_bindgen::to_value; use wasm_bindgen::JsValue; use wasm_bindgen_test::*; @@ -138,3 +138,32 @@ fn test_create_user_with_minimal_data() { assert!(user.links().is_none()); assert_eq!(user.status(), None); } + +#[wasm_bindgen_test] +fn test_parse_uri() { + // A valid URI for a post resource. + let uri = "pubky://operrr8wsbpr3ue9d4qj41ge1kcc6r7fdiy6o3ugjrrhi4y77rdo/pub/pubky.app/posts/0032SSN7Q4EVG"; + + // Call the wasm-exposed parse_uri function. + let parsed = parse_uri(uri).expect("Expected valid URI parsing"); + + // Verify the user ID is correctly parsed. + assert_eq!( + parsed.user_id(), + "operrr8wsbpr3ue9d4qj41ge1kcc6r7fdiy6o3ugjrrhi4y77rdo", + "The user ID should match the host in the URI" + ); + + // Verify that the resource string indicates a post resource. + assert!( + parsed.resource().contains("posts"), + "The resource field should indicate a posts resource" + ); + + // Verify that the resource ID is correctly extracted. + assert_eq!( + parsed.resource_id().unwrap(), + "0032SSN7Q4EVG", + "The resource_id should match the post id provided in the URI" + ); +} From 1ad68e8b4fb6247056b05e0c1234cc960fae482a Mon Sep 17 00:00:00 2001 From: SHAcollision Date: Mon, 3 Feb 2025 15:46:11 -0400 Subject: [PATCH 2/2] Add docs bump version --- pkg/README.md | 27 +++++++++++++++++++++++++++ pkg/package.json | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/pkg/README.md b/pkg/README.md index 3dba160..b6eeef7 100644 --- a/pkg/README.md +++ b/pkg/README.md @@ -165,3 +165,30 @@ This library supports many more domain objects beyond `User` and `Post`. Here ar - **LastRead**: `createLastRead(...)` Each has a `meta` field for storing relevant IDs/paths and a typed data object. + +## 📌 Parsing a Pubky URI + +The `parse_uri()` function converts a Pubky URI string into a strongly typed object. + +**Usage:** + +```js +import { parse_uri } from "pubky-app-specs"; + +try { + const result = parse_uri("pubky://userID/pub/pubky.app/posts/postID"); + console.log(result.user_id); // "userID" + console.log(result.resource); // e.g. "posts" + console.log(result.resource_id); // "postID" or null +} catch (error) { + console.error("URI parse error:", error); +} +``` + +**Returns:** + +A `ParsedUriResult` object with: + +- **user_id:** The parsed user identifier. +- **resource:** A string indicating the resource type. +- **resource_id:** An optional resource identifier. diff --git a/pkg/package.json b/pkg/package.json index a38b5b4..324ba99 100644 --- a/pkg/package.json +++ b/pkg/package.json @@ -2,7 +2,7 @@ "name": "pubky-app-specs", "type": "module", "description": "Pubky.app Data Model Specifications", - "version": "0.3.0", + "version": "0.3.0-rc1", "license": "MIT", "collaborators": [ "SHAcollision"