diff --git a/crates/observation-tools-client/openapi.json b/crates/observation-tools-client/openapi.json index 2063253..050514d 100644 --- a/crates/observation-tools-client/openapi.json +++ b/crates/observation-tools-client/openapi.json @@ -137,7 +137,7 @@ "type": "object" }, "GroupId": { - "description": "Unique identifier for a group\n\nGroup IDs are user-provided strings. By default, a UUID v7 string is generated,\nbut any string value is accepted.", + "description": "Unique identifier for a group\n\nGroup IDs are user-provided strings. By default, a UUID v7 string is\ngenerated, but any string value is accepted.", "example": "018e9a3a2c1b7e3f8d2a4b5c6d7e8f9b", "type": "string" }, @@ -326,6 +326,25 @@ ], "type": "object" }, + { + "properties": { + "Html": { + "properties": { + "raw": { + "type": "string" + } + }, + "required": [ + "raw" + ], + "type": "object" + } + }, + "required": [ + "Html" + ], + "type": "object" + }, { "properties": { "InlineBinary": { diff --git a/crates/observation-tools-client/src/observation.rs b/crates/observation-tools-client/src/observation.rs index d10df5d..c6c3e86 100644 --- a/crates/observation-tools-client/src/observation.rs +++ b/crates/observation-tools-client/src/observation.rs @@ -11,6 +11,7 @@ use crate::observation_handle::SendObservation; use crate::Error; use napi_derive::napi; use observation_tools_shared::GroupId; +use observation_tools_shared::Html; use observation_tools_shared::LogLevel; use observation_tools_shared::Markdown; use observation_tools_shared::Observation; @@ -422,4 +423,14 @@ impl ObservationBuilder { payload, } } + + /// Set the payload as HTML content, returning a builder that can be sent + #[napi(js_name = "htmlPayload")] + pub fn html_payload_napi(&self, content: String) -> ObservationBuilderWithPayloadNapi { + let payload: Payload = Html::from(content).into(); + ObservationBuilderWithPayloadNapi { + builder: self.clone(), + payload, + } + } } diff --git a/crates/observation-tools-server/src/api/observations/get.rs b/crates/observation-tools-server/src/api/observations/get.rs index d73ffe0..894901b 100644 --- a/crates/observation-tools-server/src/api/observations/get.rs +++ b/crates/observation-tools-server/src/api/observations/get.rs @@ -71,6 +71,7 @@ pub enum PayloadOrPointerResponse { Text(String), Json(serde_json::Value), Markdown { raw: String }, + Html { raw: String }, InlineBinary(Vec), Pointer { url: String }, } @@ -114,6 +115,11 @@ impl PayloadOrPointerResponse { return PayloadOrPointerResponse::Markdown { raw: text }; } } + if payload.mime_type.starts_with("text/html") { + if let Ok(text) = String::from_utf8(data.clone()) { + return PayloadOrPointerResponse::Html { raw: text }; + } + } PayloadOrPointerResponse::InlineBinary(data) } diff --git a/crates/observation-tools-shared/src/group_id.rs b/crates/observation-tools-shared/src/group_id.rs index 1770ad4..3fa547c 100644 --- a/crates/observation-tools-shared/src/group_id.rs +++ b/crates/observation-tools-shared/src/group_id.rs @@ -4,8 +4,8 @@ use utoipa::ToSchema; /// Unique identifier for a group /// -/// Group IDs are user-provided strings. By default, a UUID v7 string is generated, -/// but any string value is accepted. +/// Group IDs are user-provided strings. By default, a UUID v7 string is +/// generated, but any string value is accepted. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)] #[serde(transparent)] #[schema(value_type = String, example = "018e9a3a2c1b7e3f8d2a4b5c6d7e8f9b")] diff --git a/crates/observation-tools-shared/src/lib.rs b/crates/observation-tools-shared/src/lib.rs index 5cdb16b..d7ac47b 100644 --- a/crates/observation-tools-shared/src/lib.rs +++ b/crates/observation-tools-shared/src/lib.rs @@ -1,8 +1,8 @@ //! Shared types and models for observation-tools pub mod error; -pub mod models; mod group_id; +pub mod models; mod observation; mod payload; mod payload_id; @@ -17,6 +17,7 @@ pub use observation::Observation; pub use observation::ObservationId; pub use observation::ObservationType; pub use observation::SourceInfo; +pub use payload::Html; pub use payload::Markdown; pub use payload::Payload; pub use payload::PayloadBuilder; diff --git a/crates/observation-tools-shared/src/payload.rs b/crates/observation-tools-shared/src/payload.rs index 8306eb0..bacf95f 100644 --- a/crates/observation-tools-shared/src/payload.rs +++ b/crates/observation-tools-shared/src/payload.rs @@ -40,6 +40,17 @@ impl Payload { } } + /// Create a new payload from HTML + pub fn html(data: impl Into) -> Self { + let data = data.into().into_bytes(); + let size = data.len(); + Self { + data, + mime_type: "text/html".to_string(), + size, + } + } + /// Create a new payload with a custom MIME type pub fn with_mime_type(data: impl Into, mime_type: impl Into) -> Self { let data = data.into().into_bytes(); @@ -113,6 +124,38 @@ impl From for Payload { } } +/// A wrapper type for HTML content. +/// +/// Use this to create observations with HTML payloads that will be +/// rendered directly in the UI. +/// +/// # Example +/// ```rust +/// use observation_tools_shared::Html; +/// +/// let html = Html::from("

Hello

This is bold text.

"); +/// ``` +#[derive(Debug, Clone)] +pub struct Html { + content: String, +} + +impl Html { + /// Create a new Html payload from any type that can be converted to a + /// String. + pub fn from(content: impl Into) -> Self { + Self { + content: content.into(), + } + } +} + +impl From for Payload { + fn from(html: Html) -> Self { + Payload::with_mime_type(html.content, "text/html") + } +} + /// Builder for creating named payloads to attach to observations. /// /// Each `PayloadBuilder` pairs a name with a `Payload`, allowing observations @@ -162,4 +205,12 @@ impl PayloadBuilder { payload: Payload::with_mime_type(content, "text/markdown"), } } + + /// Create a named HTML payload + pub fn html(name: impl Into, content: impl Into) -> Self { + Self { + name: name.into(), + payload: Payload::html(content), + } + } }