snapshot of https://github.com/ActivityWatch/aw-server-rust.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Build / ubuntu-latest (push) Failing after 5s
Build / Android (push) Failing after 2s
Build / Code coverage (push) Failing after 2s
Lint / format (push) Failing after 3s
Lint / clippy (push) Failing after 3s
Build / macOS-latest (push) Has been cancelled
Build / windows-latest (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-16 00:19:17 +05:00
commit da934b693c
120 changed files with 26659 additions and 0 deletions

20
aw-models/Cargo.toml Normal file
View File

@@ -0,0 +1,20 @@
[package]
name = "aw-models"
version = "0.1.0"
authors = ["Johan Bjäreholt <johan@bjareho.lt>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "aw_models"
crate-type = ["lib", "cdylib"]
path = "src/lib.rs"
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
schemars = { version = "0.8", features = ["chrono"] }

View File

@@ -0,0 +1,8 @@
extern crate aw_models;
use schemars::schema_for;
fn main() {
let schema = schema_for!(aw_models::Bucket);
println!("{}", serde_json::to_string_pretty(&schema).unwrap());
}

60
aw-models/src/bucket.rs Normal file
View File

@@ -0,0 +1,60 @@
use chrono::DateTime;
use chrono::Utc;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::map::Map;
use serde_json::value::Value;
use std::collections::HashMap;
use crate::Event;
use crate::TryVec;
#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)]
pub struct Bucket {
#[serde(skip)]
pub bid: Option<i64>,
#[serde(default)]
pub id: String,
#[serde(rename = "type")] /* type is a reserved Rust keyword */ pub _type: String,
pub client: String,
pub hostname: String,
pub created: Option<DateTime<Utc>>,
#[serde(default)]
pub data: Map<String, Value>,
#[serde(default, skip_deserializing)]
pub metadata: BucketMetadata,
// Events should only be "Some" during import/export
// It's using a TryVec to discard only the events which were failed to be serialized so only a
// few events are being dropped during import instead of failing the whole import
pub events: Option<TryVec<Event>>,
pub last_updated: Option<DateTime<Utc>>, // TODO: Should probably be moved into metadata field
}
#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, Default)]
pub struct BucketMetadata {
#[serde(default)]
pub start: Option<DateTime<Utc>>,
pub end: Option<DateTime<Utc>>,
}
#[derive(Serialize, Deserialize, JsonSchema, Clone)]
pub struct BucketsExport {
pub buckets: HashMap<String, Bucket>,
}
#[test]
fn test_bucket() {
let b = Bucket {
bid: None,
id: "id".to_string(),
_type: "type".to_string(),
client: "client".to_string(),
hostname: "hostname".into(),
created: None,
data: json_map! {},
metadata: BucketMetadata::default(),
events: None,
last_updated: None,
};
debug!("bucket: {:?}", b);
}

19
aw-models/src/duration.rs Normal file
View File

@@ -0,0 +1,19 @@
use serde::{Deserialize, Serialize};
// Max duration of a i64 nanosecond is 2562047.7880152157 hours
// ((2**64)/2)/1000000000/60/60
fn get_nanos(duration: &chrono::Duration) -> f64 {
(duration.num_nanoseconds().unwrap() as f64) / 1_000_000_000.0
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "chrono::Duration")]
pub struct DurationSerialization(#[serde(getter = "get_nanos")] f64);
// Provide a conversion to construct the remote type.
impl From<DurationSerialization> for chrono::Duration {
fn from(def: DurationSerialization) -> chrono::Duration {
chrono::Duration::nanoseconds((def.0 * 1_000_000_000.0) as i64)
}
}

101
aw-models/src/event.rs Normal file
View File

@@ -0,0 +1,101 @@
use chrono::DateTime;
use chrono::Duration;
use chrono::Utc;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Map;
use serde_json::Value;
use crate::duration::DurationSerialization;
use crate::TimeInterval;
#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)]
pub struct Event {
/// An unique id for this event.
/// Will be assigned once the event has reached the servers datastore.
///
/// **WARNING:** If you set the ID and insert the event to the server it will replace the previous
/// event with that ID. Only do this if you are completely sure what you are doing.
pub id: Option<i64>,
/// An rfc3339 timestamp which represents the start of the event
pub timestamp: DateTime<Utc>,
/// Duration of the event as a floating point number in seconds.
/// Appended to the timestamp it can represent the end of the event
/// Maximum precision is nanoseconds.
#[serde(with = "DurationSerialization", default = "default_duration")]
#[schemars(with = "f64")]
pub duration: Duration,
/// Can contain any arbitrary JSON data that represents the value of the event.
/// All events in a bucket should follow the format of it's respective bucket-type.
pub data: Map<String, Value>,
}
impl Event {
pub fn new(timestamp: DateTime<Utc>, duration: Duration, data: Map<String, Value>) -> Self {
Event {
id: None,
timestamp,
duration,
data,
}
}
pub fn calculate_endtime(&self) -> DateTime<Utc> {
self.timestamp + chrono::Duration::nanoseconds(self.duration.num_nanoseconds().unwrap())
}
pub fn interval(&self) -> TimeInterval {
TimeInterval::new(self.timestamp, self.calculate_endtime())
}
}
impl PartialEq for Event {
fn eq(&self, other: &Event) -> bool {
!(self.timestamp != other.timestamp
|| self.duration != other.duration
|| self.data != other.data)
}
}
impl Default for Event {
fn default() -> Self {
Event {
id: None,
timestamp: Utc::now(),
duration: Duration::seconds(0),
data: serde_json::Map::new(),
}
}
}
fn default_duration() -> Duration {
Duration::seconds(0)
}
#[test]
fn test_event() {
use serde_json::json;
let e = Event {
id: None,
timestamp: Utc::now(),
duration: Duration::seconds(1),
data: json_map! {"test": json!(1)},
};
debug!("event: {:?}", e);
}
#[test]
fn test_event_default_duration() {
// Watchers may omit duration when posting an event, in which case it
// defaults to zero rather than failing to deserialize.
let e: Event =
serde_json::from_str(r#"{"timestamp": "2000-01-01T00:00:00Z", "data": {"test": 1}}"#)
.unwrap();
assert_eq!(e.duration, Duration::seconds(0));
// An explicit duration is still honoured.
let e: Event = serde_json::from_str(
r#"{"timestamp": "2000-01-01T00:00:00Z", "duration": 1.5, "data": {}}"#,
)
.unwrap();
assert_eq!(e.duration, Duration::milliseconds(1500));
}

4
aw-models/src/export.rs Normal file
View File

@@ -0,0 +1,4 @@
#[derive(Serialize, Deserialize, JsonSchema, Clone)]
pub struct BucketsExport {
pub buckets: HashMap<String, Bucket>,
}

19
aw-models/src/info.rs Normal file
View File

@@ -0,0 +1,19 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct Info {
pub hostname: String,
pub version: String,
pub testing: bool,
/// Name of the running instance profile ("default" unless `--profile` was
/// given). Lets clients tell concurrent instances apart. Defaults on
/// deserialization so a new client can still talk to an older server.
#[serde(default = "default_profile")]
pub profile: String,
pub device_id: String,
}
fn default_profile() -> String {
"default".to_string()
}

39
aw-models/src/lib.rs Normal file
View File

@@ -0,0 +1,39 @@
#[macro_use]
extern crate log;
// TODO: Move me to an appropriate place
#[cfg(test)] // Only macro use for tests
macro_rules! json_map {
{ $( $key:literal : $value:expr),* } => {{
use serde_json::{Value};
use serde_json::map::Map;
#[allow(unused_mut)]
let mut map : Map<String, Value> = Map::new();
$(
map.insert( $key.to_string(), json!($value) );
)*
map
}};
}
mod bucket;
mod duration;
mod event;
mod info;
mod query;
mod settings;
mod timeinterval;
mod tryvec;
pub use self::bucket::Bucket;
pub use self::bucket::BucketMetadata;
pub use self::bucket::BucketsExport;
pub use self::event::Event;
pub use self::info::Info;
pub use self::query::Query;
pub use self::settings::Settings;
pub use self::settings::{
Class, ClassData, ClassRule, NewReleaseCheckData, UserSatisfactionPollData, View, ViewElement,
};
pub use self::timeinterval::TimeInterval;
pub use self::tryvec::TryVec;

38
aw-models/src/query.rs Normal file
View File

@@ -0,0 +1,38 @@
use serde::{Deserialize, Serialize};
use crate::TimeInterval;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Query {
//#[serde(with = "DurationSerialization")]
pub timeperiods: Vec<TimeInterval>,
pub query: Vec<String>,
}
#[test]
fn test_query_serialization_roundtrip() {
for payload in [
serde_json::json!({
"timeperiods": [
"2000-01-01T00:00:00+00:00/2000-01-02T00:00:00+00:00",
"2000-01-02T00:00:00.123456789+00:00/2000-01-03T00:00:00+00:00"
],
"query": ["events = query_bucket(\"test\");\nRETURN = events;", "RETURN = [];" ]
}),
serde_json::json!({"timeperiods": [], "query": []}),
] {
let query: Query = serde_json::from_value(payload.clone()).unwrap();
let encoded = serde_json::to_string(&query).unwrap();
assert_eq!(
serde_json::from_str::<serde_json::Value>(&encoded).unwrap(),
payload
);
let decoded: Query = serde_json::from_str(&encoded).unwrap();
assert_eq!(decoded.query, query.query);
assert_eq!(decoded.timeperiods.len(), query.timeperiods.len());
for (actual, expected) in decoded.timeperiods.iter().zip(&query.timeperiods) {
assert_eq!(actual.start(), expected.start());
assert_eq!(actual.end(), expected.end());
}
}
}

76
aw-models/src/settings.rs Normal file
View File

@@ -0,0 +1,76 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, JsonSchema, Clone)]
pub struct NewReleaseCheckData {
pub how_often_to_check: i64,
pub is_enabled: bool,
pub next_check_time: String,
pub times_checked: i32,
}
#[derive(Serialize, Deserialize, JsonSchema, Clone)]
pub struct UserSatisfactionPollData {
pub is_enabled: bool,
pub next_poll_time: String,
pub times_poll_is_shown: i32,
}
#[derive(Serialize, Deserialize, JsonSchema, Clone)]
pub struct ViewElement {
pub size: i32,
#[serde(rename = "type")]
pub element_type: String,
}
#[derive(Serialize, Deserialize, JsonSchema, Clone)]
pub struct View {
pub elements: Vec<ViewElement>,
pub id: String,
pub name: String,
}
#[derive(Serialize, Deserialize, JsonSchema, Clone)]
pub struct ClassData {
#[serde(skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub score: Option<i32>,
}
#[derive(Serialize, Deserialize, JsonSchema, Clone)]
pub struct ClassRule {
#[serde(skip_serializing_if = "Option::is_none")]
pub ignore_case: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub regex: Option<String>,
#[serde(rename = "type")]
pub rule_type: String,
}
#[derive(Serialize, Deserialize, JsonSchema, Clone)]
pub struct Class {
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<ClassData>,
pub id: i32,
pub name: Vec<String>,
pub rule: ClassRule,
}
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct Settings {
pub landing_page: String,
pub start_of_day: String,
pub always_active_pattern: String,
pub new_release_check_data: NewReleaseCheckData,
pub start_of_week: String,
pub use_color_fallback: bool,
pub user_satisfaction_poll_data: UserSatisfactionPollData,
pub request_timeout: i32,
pub devmode: bool,
pub theme: String,
pub views: Vec<View>,
pub duration_default: i32,
pub classes: Vec<Class>,
pub initial_timestamp: String,
}

View File

@@ -0,0 +1,211 @@
use crate::Event;
use std::cmp::{max, min};
use std::fmt;
use serde::de::{self, Deserialize, Deserializer, Visitor};
use serde::{Serialize, Serializer};
use chrono::DateTime;
use chrono::Duration;
use chrono::Utc;
#[derive(Clone, Debug)]
pub struct TimeInterval {
start: DateTime<Utc>,
end: DateTime<Utc>,
}
#[derive(Debug)]
pub enum TimeIntervalError {
ParseError(),
}
/// Python versions of many of these functions can be found at https://github.com/ErikBjare/timeslot
impl TimeInterval {
pub fn new(start: DateTime<Utc>, end: DateTime<Utc>) -> TimeInterval {
TimeInterval { start, end }
}
pub fn new_from_string(period: &str) -> Result<TimeInterval, TimeIntervalError> {
let splits = period.split('/').collect::<Vec<&str>>();
if splits.len() != 2 {
return Err(TimeIntervalError::ParseError());
}
let start = match DateTime::parse_from_rfc3339(splits[0]) {
Ok(dt) => dt.with_timezone(&Utc),
Err(_e) => return Err(TimeIntervalError::ParseError()),
};
let end = match DateTime::parse_from_rfc3339(splits[1]) {
Ok(dt) => dt.with_timezone(&Utc),
Err(_e) => return Err(TimeIntervalError::ParseError()),
};
Ok(TimeInterval::new(start, end))
}
pub fn start(&self) -> &DateTime<Utc> {
&self.start
}
pub fn end(&self) -> &DateTime<Utc> {
&self.end
}
pub fn duration(&self) -> Duration {
self.end - self.start
}
/// If intervals are separated by a non-zero gap, return the gap as a new TimeInterval, else None
pub fn gap(&self, other: &TimeInterval) -> Option<TimeInterval> {
if self.end < other.start {
Some(TimeInterval::new(self.end, other.start))
} else if other.end < self.start {
Some(TimeInterval::new(other.end, self.start))
} else {
None
}
}
/// Joins two intervals together if they don't have a gap, else None
pub fn union(&self, other: &TimeInterval) -> Option<TimeInterval> {
match self.gap(other) {
Some(_) => None,
None => Some(TimeInterval::new(
min(self.start, other.start),
max(self.end, other.end),
)),
}
}
/// The intersection of two intervals
pub fn intersection(&self, other: &TimeInterval) -> Option<TimeInterval> {
let last_start = max(self.start, other.start);
let first_end = min(self.end, other.end);
if last_start < first_end {
Some(TimeInterval::new(last_start, first_end))
} else {
None
}
}
/// A boolean whether the two intervals intersect
pub fn intersects(&self, other: &TimeInterval) -> bool {
self.intersection(other).is_some()
}
}
impl From<&Event> for TimeInterval {
fn from(e: &Event) -> TimeInterval {
TimeInterval::new(e.timestamp, e.timestamp + e.duration)
}
}
impl fmt::Display for TimeInterval {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}/{}", self.start.to_rfc3339(), self.end.to_rfc3339())
}
}
struct TimeIntervalVisitor;
use serde::de::Unexpected;
impl<'de> Visitor<'de> for TimeIntervalVisitor {
type Value = TimeInterval;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an string in ISO timeinterval format (such as 2000-01-01T00:00:00+01:00/2001-02-02T01:01:01+01:00)")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match TimeInterval::new_from_string(value) {
Ok(ti) => Ok(ti),
Err(e) => {
warn!("{:?}", e);
Err(de::Error::invalid_value(Unexpected::Str(value), &self))
}
}
}
}
impl<'de> Deserialize<'de> for TimeInterval {
fn deserialize<D>(deserializer: D) -> Result<TimeInterval, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(TimeIntervalVisitor)
}
}
impl Serialize for TimeInterval {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
#[test]
fn test_timeinterval_serialization() {
for (input, expected) in [
(
"2000-01-01T00:00:00Z/2000-01-02T00:00:00Z",
"2000-01-01T00:00:00+00:00/2000-01-02T00:00:00+00:00",
),
(
"2000-01-01T03:00:00.123456789+03:00/2000-01-01T01:00:00.987654321-02:00",
"2000-01-01T00:00:00.123456789+00:00/2000-01-01T03:00:00.987654321+00:00",
),
(
"2000-01-01T00:00:00Z/2000-01-01T00:00:00Z",
"2000-01-01T00:00:00+00:00/2000-01-01T00:00:00+00:00",
),
] {
let interval = TimeInterval::new_from_string(input).unwrap();
let encoded = serde_json::to_value(&interval).unwrap();
assert_eq!(encoded, serde_json::json!(expected));
let decoded: TimeInterval = serde_json::from_value(encoded).unwrap();
assert_eq!(decoded.start(), interval.start());
assert_eq!(decoded.end(), interval.end());
}
}
#[test]
fn test_timeinterval() {
use std::str::FromStr;
let start = DateTime::from_str("2000-01-01T00:00:00Z").unwrap();
let end = DateTime::from_str("2000-01-02T00:00:00Z").unwrap();
let period_str = "2000-01-01T00:00:00+00:00/2000-01-02T00:00:00+00:00";
let duration = end - start;
let tp = TimeInterval::new(start, end);
assert_eq!(tp.start(), &start);
assert_eq!(tp.end(), &end);
assert_eq!(tp.duration(), duration);
assert_eq!(tp.to_string(), period_str);
let tp = TimeInterval::new_from_string(period_str).unwrap();
assert_eq!(tp.start(), &start);
assert_eq!(tp.end(), &end);
assert_eq!(tp.duration(), duration);
assert_eq!(tp.to_string(), period_str);
}
#[test]
fn test_timeinterval_intersection() {
use std::str::FromStr;
// Check that two exactly adjacent events don't intersect
let tp1 = TimeInterval::new(
DateTime::from_str("2000-01-01T00:00:00Z").unwrap(),
DateTime::from_str("2000-01-01T00:01:00Z").unwrap(),
);
let tp2 = TimeInterval::new(
DateTime::from_str("2000-01-01T00:01:00Z").unwrap(),
DateTime::from_str("2000-01-01T00:02:00Z").unwrap(),
);
assert!(!tp1.intersects(&tp2));
}

208
aw-models/src/tryvec.rs Normal file
View File

@@ -0,0 +1,208 @@
use core::marker::PhantomData;
use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
use serde::de::{DeserializeOwned, SeqAccess, Visitor};
use serde::ser::SerializeSeq;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use std::fmt;
use std::fmt::Debug;
#[derive(Debug, Clone)]
pub enum TryParse<T: JsonSchema> {
Parsed(T),
#[allow(dead_code)]
Unparsed(Value),
NotPresent,
}
impl<T: JsonSchema> JsonSchema for TryParse<T> {
fn schema_name() -> String {
format!("Try<{}>", std::any::type_name::<T>())
}
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
gen.subschema_for::<T>()
}
}
impl<'de, T: DeserializeOwned + JsonSchema> Deserialize<'de> for TryParse<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
match Option::<Value>::deserialize(deserializer)? {
None => Ok(TryParse::NotPresent),
Some(value) => match T::deserialize(&value) {
Ok(t) => Ok(TryParse::Parsed(t)),
Err(_) => Ok(TryParse::Unparsed(value)),
},
}
}
}
#[derive(Debug, Clone, JsonSchema)]
#[serde(transparent)]
pub struct TryVec<T: JsonSchema> {
inner: Vec<TryParse<T>>,
}
impl<T: JsonSchema> TryVec<T> {
pub fn new(mut vec: Vec<T>) -> Self {
let mut vec_marked: Vec<TryParse<T>> = Vec::new();
for item in vec.drain(..) {
vec_marked.push(TryParse::Parsed(item));
}
TryVec { inner: vec_marked }
}
pub fn new_empty() -> Self {
TryVec { inner: Vec::new() }
}
pub fn take_inner(self) -> Vec<T> {
let mut vec: Vec<T> = Vec::new();
for item in self.inner {
match item {
TryParse::Parsed(i) => vec.push(i),
_ => continue,
};
}
vec
}
}
impl<T: JsonSchema> Serialize for TryVec<T>
where
T: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.inner.len()))?;
for element in &self.inner {
match element {
TryParse::Parsed(t) => seq.serialize_element(t)?,
_ => continue,
};
}
seq.end()
}
}
struct TryVecVisitor<T: JsonSchema> {
marker: PhantomData<fn() -> TryVec<T>>,
}
impl<T: JsonSchema> TryVecVisitor<T> {
fn new() -> Self {
TryVecVisitor {
marker: PhantomData,
}
}
}
impl<'de, T> Visitor<'de> for TryVecVisitor<T>
where
T: DeserializeOwned + JsonSchema,
{
type Value = TryVec<T>;
// Format a message stating what data this Visitor expects to receive.
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a sequence")
}
fn visit_seq<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: SeqAccess<'de>,
{
let mut vec = Vec::new();
loop {
let res = match access.next_element() {
Ok(val) => val,
Err(err) => {
println!("Failed to parse event because '{err}', the event will be discarded");
continue;
}
};
match res {
Some(item) => vec.push(item),
None => break,
};
}
Ok(TryVec { inner: vec })
}
}
impl<'de, T: JsonSchema> Deserialize<'de> for TryVec<T>
where
T: DeserializeOwned,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_seq(TryVecVisitor::new())
}
}
#[cfg(test)]
mod test {
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::TryVec;
#[derive(Deserialize, Serialize, JsonSchema, Debug)]
struct TestEvent {
data: String,
}
fn assert_serialized_deserialized_eq(data: &str, eq: &str) {
let deserialized = serde_json::from_str::<TryVec<TestEvent>>(data).unwrap();
let serialized = serde_json::to_string(&deserialized).unwrap();
assert_eq!(serialized, eq);
}
#[test]
fn test_serialize_deserialize() {
println!("test empty array");
assert_serialized_deserialized_eq(r#"[]"#, r#"[]"#);
println!("test one valid event");
assert_serialized_deserialized_eq(r#"[{"data":"test"}]"#, r#"[{"data":"test"}]"#);
println!("test invalid type int, skip event");
assert_serialized_deserialized_eq(r#"[{ "data": 1 }]"#, r#"[]"#);
println!("test invalid type dict, skip event");
assert_serialized_deserialized_eq(r#"[{"data":{}}]"#, r#"[]"#);
println!("test invalid type arr, skip event");
assert_serialized_deserialized_eq(r#"[{"data":[]}]"#, r#"[]"#);
println!("test multiple valid events");
assert_serialized_deserialized_eq(
r#"[{"data":"test"},{"data":"test2"},{"data":"test3"}]"#,
r#"[{"data":"test"},{"data":"test2"},{"data":"test3"}]"#,
);
println!("test invalid event in middle of sequence, skip one event");
assert_serialized_deserialized_eq(
r#"[{"data":"test"},{"data":2},{"data":"test3"}]"#,
r#"[{"data":"test"},{"data":"test3"}]"#,
);
println!("test utf-16 character");
assert_serialized_deserialized_eq(r#"[{"data":"\ud835\udc47"}]"#, r#"[{"data":"𝑇"}]"#);
println!("test invalid utf-8/16, skip event");
assert_serialized_deserialized_eq(r#"[{"data":"\ud835"}]"#, r#"[]"#);
}
#[test]
fn test_methods() {
let tryvec = TryVec::<TestEvent>::new_empty();
assert_eq!(tryvec.take_inner().len(), Vec::<TestEvent>::new().len());
}
}