diff --git a/worker-sandbox/src/analytics_engine.rs b/worker-sandbox/src/analytics_engine.rs index 6870320b..f9aa9ffa 100644 --- a/worker-sandbox/src/analytics_engine.rs +++ b/worker-sandbox/src/analytics_engine.rs @@ -1,7 +1,6 @@ use super::SomeSharedData; -use worker::{ - AnalyticsEngineDataPoint, AnalyticsEngineDataPointBuilder, Env, Request, Response, Result, -}; +use uuid::Uuid; +use worker::{AnalyticsEngineDataPointBuilder, Env, Request, Response, Result}; #[worker::send] pub async fn handle_analytics_event( @@ -14,21 +13,23 @@ pub async fn handle_analytics_event( Err(err) => return Response::error(format!("Failed to get dataset: {err:?}"), 500), }; - // String blobs - let event: AnalyticsEngineDataPoint = AnalyticsEngineDataPointBuilder::new() - .indexes(vec!["http".into()]) - .blobs(vec![req.method().to_string()]) - .doubles(vec![200.into()]) + let request_id = Uuid::new_v4(); + // Build the event and write it to analytics engine + let point = AnalyticsEngineDataPointBuilder::new() + .indexes(vec!["index1"].as_slice()) + .add_blob(req.method().as_ref()) // blob1 + .add_blob(request_id.as_bytes().as_ref()) // blob2 + .add_double(200) .build(); - dataset.write_data_point(&event)?; + dataset.write_data_point(&point)?; - // Binary blobs - let event: AnalyticsEngineDataPoint> = AnalyticsEngineDataPointBuilder::new() - .indexes(vec!["http".into()]) - .blobs(vec![req.method().to_string().as_bytes().to_vec()]) - .doubles(vec![200.into()]) - .build(); - dataset.write_data_point(&event)?; + // Or write it directly from the builder using write_to + AnalyticsEngineDataPointBuilder::new() + .indexes(vec!["index1"].as_slice()) + .add_blob(req.method().as_ref()) // blob1 + .add_blob(req.method().as_ref()) // blob2 + .add_double(200) + .write_to(&dataset)?; return Response::ok("Events sent"); } diff --git a/worker/src/analytics_engine.rs b/worker/src/analytics_engine.rs index 430e3188..2a99fa5a 100644 --- a/worker/src/analytics_engine.rs +++ b/worker/src/analytics_engine.rs @@ -1,8 +1,8 @@ use crate::EnvBinding; use crate::Result; -use js_sys::Array; -use js_sys::Object; -use std::marker::PhantomData; +use js_sys::{Array, Uint8Array}; +use js_sys::{JsString, Object}; +use std::convert::TryFrom; use wasm_bindgen::{JsCast, JsValue}; use worker_sys::AnalyticsEngineDataset as AnalyticsEngineSys; @@ -51,123 +51,264 @@ impl AsRef for AnalyticsEngineDataset { } impl AnalyticsEngineDataset { - pub fn write_data_point(&self, event: &AnalyticsEngineDataPoint) -> Result<()> + pub fn write_data_point(&self, event: &AnalyticsEngineDataPoint) -> Result<()> where - T: BlobType, - AnalyticsEngineDataPoint: Clone, - JsValue: From>, + AnalyticsEngineDataPoint: Clone, + JsValue: From, { Ok(self.0.write_data_point(event.to_js_object()?)?) } } -// Marker traits to constrain T -pub trait BlobType {} -impl BlobType for String {} -impl BlobType for Vec {} +pub enum BlobType { + String(String), + Blob(Vec), +} +impl Into for BlobType { + fn into(self) -> JsValue { + match self { + BlobType::String(s) => JsValue::from_str(&s), + BlobType::Blob(b) => { + let value = Uint8Array::from(b.as_slice()); + value.into() + } + } + } +} + +impl From<&str> for BlobType { + fn from(value: &str) -> Self { + BlobType::String(value.to_string()) + } +} -#[derive(Clone, Debug)] -pub struct AnalyticsEngineDataPoint -where - T: BlobType, -{ - indexes: Vec, - doubles: Vec, - blobs: Vec, +impl From for BlobType { + fn from(value: String) -> Self { + BlobType::String(value) + } } -pub struct AnalyticsEngineDataPointBuilder { - indexes: Vec, - doubles: Vec, - blobs: Vec, - _phantom: PhantomData, +impl From<&[u8]> for BlobType { + fn from(value: &[u8]) -> Self { + BlobType::Blob(value.to_vec()) + } } -impl AnalyticsEngineDataPointBuilder { +impl From> for BlobType { + fn from(value: Vec) -> Self { + BlobType::Blob(value) + } +} + +#[derive(Clone)] +pub struct AnalyticsEngineDataPoint { + indexes: Array, + doubles: Array, + blobs: Array, +} + +pub struct AnalyticsEngineDataPointBuilder { + indexes: Array, + doubles: Array, + blobs: Array, +} + +impl AnalyticsEngineDataPointBuilder { pub fn new() -> Self { Self { - indexes: Vec::new(), - doubles: Vec::new(), - blobs: Vec::new(), - _phantom: PhantomData, + indexes: Array::new(), + doubles: Array::new(), + blobs: Array::new(), } } - pub fn indexes(mut self, index: impl Into>) -> Self { - self.indexes = index.into(); + /// Sets the index values for the data point. + /// While the indexes field accepts an array, you currently must *only* provide a single index. + /// If you attempt to provide multiple indexes, your data point will not be recorded. + /// + /// # Arguments + /// + /// * `index`: A string or byte-array value to use as the index. + /// + /// returns: AnalyticsEngineDataPointBuilder + /// + /// # Examples + /// + /// ``` + /// use worker::AnalyticsEngineDataPointBuilder; + /// + /// let data = AnalyticsEngineDataPointBuilder::new() + /// .indexes(vec!["index1"].as_slice()) + /// .build(); + /// ``` + pub fn indexes(self, indexes: &[&str]) -> Self { + for idx in indexes { + self.indexes.push(&JsValue::from_str(idx)); + } + self + } + + /// Adds a numeric value to the end of the array of doubles. + /// + /// # Arguments + /// + /// * `double`: The numeric values that you want to record in your data point + /// + /// returns: AnalyticsEngineDataPointBuilder + /// + /// # Examples + /// + /// ``` + /// use worker::AnalyticsEngineDataPointBuilder; + /// let point = AnalyticsEngineDataPointBuilder::new() + /// .indexes(vec!["index1"].into()) + /// .add_double(25) // double1 + /// .add_double(0.5) // double2 + /// .build(); + /// println!("{:?}", point); + /// ``` + pub fn add_double(self, double: impl Into) -> Self { + self.doubles.push(&JsValue::from_f64(double.into())); + self + } + + /// Set doubles1-20 with the provide values. This method will remove any doubles previously + /// added using the `add_double` method. + /// + /// # Arguments + /// + /// * `doubles`: An array of doubles + /// + /// returns: AnalyticsEngineDataPointBuilder + /// + /// # Examples + /// + /// ``` + /// use worker::AnalyticsEngineDataPointBuilder; + /// let point = AnalyticsEngineDataPointBuilder::new() + /// .indexes(vec!["index1"].into()) + /// .add_double(1) // value will be replaced by the following line + /// .doubles(vec![1, 2, 3].into()) // sets double1, double2 and double3 + /// .build(); + /// println!("{:?}", point); + /// ``` + pub fn doubles(mut self, doubles: &[f64]) -> Self { + let values = Array::new(); + for n in doubles { + values.push(&JsValue::from_f64(*n)); + } + self.doubles = values; self } - pub fn doubles(mut self, doubles: impl Into>) -> Self { - self.doubles = doubles.into(); + /// Adds a blob-like value to the end of the array of blobs. + /// + /// # Arguments + /// + /// * `blob`: The blob values that you want to record in your data point + /// + /// returns: AnalyticsEngineDataPointBuilder + /// + /// # Examples + /// + /// ``` + /// use worker::AnalyticsEngineDataPointBuilder; + /// let point = AnalyticsEngineDataPointBuilder::new() + /// .indexes(vec!["index1"].into()) + /// .add_blob("Seattle") // blob1 + /// .add_blob("USA") // blob2 + /// .add_blob("pro_sensor_9000") // blob3 + /// .build(); + /// println!("{:?}", point); + /// ``` + pub fn add_blob(self, blob: impl Into) -> Self { + let v = blob.into(); + self.blobs.push(&v.into()); self } - pub fn blobs(mut self, blobs: impl Into>) -> Self { - self.blobs = blobs.into(); + /// Sets blobs1-20 with the provided array, replacing any values previously stored using `add_blob`. + /// + /// # Arguments + /// + /// * `blob`: The blob values that you want to record in your data point + /// + /// returns: AnalyticsEngineDataPointBuilder + /// + /// # Examples + /// + /// ``` + /// use worker::AnalyticsEngineDataPointBuilder; + /// let point = AnalyticsEngineDataPointBuilder::new() + /// .indexes(vec!["index1"].into()) + /// .blobs(vec!["Seattle", "USA", "pro_sensor_9000"]) // sets blob1, blob2, and blob3 + /// .build(); + /// println!("{:?}", point); + /// ``` + pub fn blobs(mut self, blobs: Vec>) -> Self { + let values = Array::new(); + for blob in blobs { + let value = blob.into(); + values.push(&value.into()); + } + self.blobs = values; self } - pub fn build(self) -> AnalyticsEngineDataPoint { + pub fn build(self) -> AnalyticsEngineDataPoint { AnalyticsEngineDataPoint { indexes: self.indexes, doubles: self.doubles, blobs: self.blobs, } } -} - -// Implement From for JsValue separately for each type -impl From> for JsValue { - fn from(event: AnalyticsEngineDataPoint) -> Self { - let obj = Object::new(); - - let indexes = Array::new(); - for index in event.indexes { - indexes.push(&JsValue::from_str(&index)); - } - js_sys::Reflect::set(&obj, &JsValue::from_str("indexes"), &indexes).unwrap(); - - let doubles = Array::new(); - for double in event.doubles { - doubles.push(&JsValue::from_f64(double)); - } - js_sys::Reflect::set(&obj, &JsValue::from_str("doubles"), &doubles).unwrap(); - let blobs = Array::new(); - for blob in event.blobs { - blobs.push(&JsValue::from_str(&blob)); - } - js_sys::Reflect::set(&obj, &JsValue::from_str("blobs"), &blobs).unwrap(); - - JsValue::from(obj) + /// Write the data point to the provided analytics engine dataset. This is a convenience method + /// that can be used in place of a `.build()` followed by a call to `dataset.write_data_point(point)`. + /// + /// # Arguments + /// + /// * `dataset`: Analytics engine dataset binding + /// + /// returns: worker::Result<()> + /// + /// # Examples + /// + /// ``` + /// use worker::{Env, AnalyticsEngineDataPointBuilder, Response}; + /// use std::io::Error; + /// + /// fn main(env: Env) -> worker::Result { + /// let dataset = match env.analytics_engine("HTTP_ANALYTICS") { + /// Ok(dataset) => dataset, + /// Err(err) => return Response::error(format!("Failed to get dataset: {err:?}"), 500), + /// }; + /// + /// AnalyticsEngineDataPointBuilder::new() + /// .indexes(vec!["index1"].as_slice()) + /// .add_blob("GET") // blob1 + /// .add_double(200) // double1 + /// .write_to(&dataset)?; + /// + /// Response::ok("OK") + /// } + /// ``` + pub fn write_to(self, dataset: &AnalyticsEngineDataset) -> Result<()> { + dataset.write_data_point(&self.build()) } } -impl From>> for JsValue { - fn from(event: AnalyticsEngineDataPoint>) -> Self { +// Implement From for JsValue separately for each type +impl From for JsValue { + fn from(event: AnalyticsEngineDataPoint) -> Self { let obj = Object::new(); - let indexes = Array::new(); - for index in event.indexes { - indexes.push(&JsValue::from_str(&index)); - } - js_sys::Reflect::set(&obj, &JsValue::from_str("indexes"), &indexes).unwrap(); + js_sys::Reflect::set(&obj, &JsValue::from_str("indexes"), &event.indexes).unwrap(); + js_sys::Reflect::set(&obj, &JsValue::from_str("doubles"), &event.doubles).unwrap(); - let doubles = Array::new(); - for double in event.doubles { - doubles.push(&JsValue::from_f64(double)); - } - js_sys::Reflect::set(&obj, &JsValue::from_str("doubles"), &doubles).unwrap(); - - // An array of u8 arrays. let blobs = Array::new(); for blob in event.blobs { - let slice = Array::new(); - for byte in blob { - slice.push(&JsValue::from_f64(byte as f64)); - } - blobs.push(&JsValue::from(slice)); + blobs.push(&JsValue::from(&blob)); } js_sys::Reflect::set(&obj, &JsValue::from_str("blobs"), &blobs).unwrap(); @@ -175,7 +316,7 @@ impl From>> for JsValue { } } -impl AnalyticsEngineDataPoint { +impl AnalyticsEngineDataPoint { pub fn to_js_object(&self) -> Result where Self: Clone,