typed IDs for most things, docs for all

This commit is contained in:
Dan Ballard 2022-07-14 16:33:41 -07:00
parent bd5d3f7e5f
commit 384c5880b2
2 changed files with 304 additions and 72 deletions

View File

@ -4,13 +4,69 @@ use chrono::{DateTime, FixedOffset};
use chrono::format::Fixed::TimezoneOffset;
use chrono::prelude::*;
use chrono::offset::LocalResult;
use std::convert::From;
use crate::structs::{ACL, ConnectionState, CwtchEvent};
pub type Identity = String;
pub type Handle = String;
pub type ConversationID = i32;
pub type FileKey = String;
#[derive(Debug)]
/// Profile ID used to refer to profiles in Cwtch
pub struct ProfileIdentity(String);
impl From<String> for ProfileIdentity {
fn from(x: String) -> Self {
ProfileIdentity(x)
}
}
#[derive(Debug)]
/// Contact ID used to refer to contacts in Cwtch
pub struct ContactIdentity(String);
impl From<String> for ContactIdentity {
fn from(x: String) -> Self {
ContactIdentity(x)
}
}
#[derive(Debug)]
/// Conversation ID user to refer to a conversation with a Contact or Group in Cwtch
pub struct ConversationID(i32) ;
impl From<i32> for ConversationID {
fn from(x: i32) -> Self {
ConversationID(x)
}
}
#[derive(Debug)]
/// Group ID used to refer to a Group in Cwtch
pub struct GroupID(String) ;
impl From<String> for GroupID {
fn from(x: String) -> Self {
GroupID(x)
}
}
#[derive(Debug)]
/// Server ID user to refer to a server in Cwtch
pub struct ServerIdentity(String);
impl From<String> for ServerIdentity {
fn from(x: String) -> Self {
ServerIdentity(x)
}
}
#[derive(Debug)]
/// FileKey ID user to refer to a file share in Cwtch
pub struct FileKey(String);
impl From<String> for FileKey {
fn from(x: String) -> Self {
FileKey(x)
}
}
#[derive(Debug)]
/// Enum for type of notification a UI/client should emit for a new message
@ -104,31 +160,44 @@ pub enum Event {
CwtchStarted,
/// A new peer has been loaded, details of peer. Identity should be stored so further API calls can be made
NewPeer {
identity: Identity,
/// identity field
profile: ProfileIdentity,
/// optional client specified tag
tag: String,
/// is this a newly created profile event, or a load
created: bool,
/// user supplied name of the profile
name: String,
/// default picture path
default_picture: String,
/// user supplied picture path
picture: String,
/// is the profile online
online: String,
/// json of the contacts
contacts_json: String,
/// json of known servers
server_json: String, //ServerList
},
/// Cwtch had an error at the app level (not profile level), usually in response to an API call
AppError {
/// details of the app error that occured
error: String
},
/// Global settings being emited from lcg, usually in response to them being sent to be saved by client
UpdateGlobalSettings {
/// map of setting names to values (https://git.openprivacy.ca/cwtch.im/libcwtch-go/src/branch/trunk/utils/settings.go)
settings: HashMap<String, String>,
},
/// A profile has an error, usually emited in response to an API call
PeerError {
/// details of the peer error that occured
error: String
},
/// A profile was successfully deleted, it is no longer usable
PeerDeleted {
identity: String
/// identity of deleted peer
profile: ProfileIdentity
},
/// Cwtch is shutting down, stop making API calls
Shutdown,
@ -141,7 +210,8 @@ pub enum Event {
},
/// Version of the ACN (currently tor)
ACNVersion {
verstion: String,
/// version string from ACN app
version: String,
},
/// Notice from libCwtch that before completing load of a profile a storage migration is occuring so Start will take longer
StartingStorageMiragtion,
@ -152,28 +222,42 @@ pub enum Event {
/// A new server has been loaded
NewServer {
onion: Identity,
/// identity of server
server: ServerIdentity,
/// sharable / importable server bundle
server_bundle: String,
/// user supplied description
description: String,
/// storage mode the server is using
storage_type: ServerStorageType,
/// does/should the server auto start on cwtch start
autostart: bool,
/// is the server running
running: bool,
},
/// Response to request for server intent change, indicating the server is indending the new state
ServerIntentUpdate {
identity: Identity,
/// identity of server
server: ServerIdentity,
/// intent of the server to be running or not
intent: ServerIntent
},
/// Notice a server was deleted (in response to an API call) and is no longer usable
ServerDeleted {
identity: Identity,
/// identity of server
server: ServerIdentity,
/// was deletion a success
success: bool,
/// optional error string in case of failure to delete
error: Option<String>,
},
/// Stats info for a server, periodically emited
ServerStatsUpdate {
identity: Identity,
/// identity of server
server: ServerIdentity,
/// count of total messages on the server
total_messages: i32,
/// count of total current connections to the server
connections: i32,
},
@ -181,151 +265,277 @@ pub enum Event {
/// A new message was received
NewMessageFromPeer {
conversation_id: i32,
handle: String,
/// identity field
profile: ProfileIdentity,
/// conversation id
conversation_id: ConversationID,
/// contact id
contact: ContactIdentity,
/// name of contact
nick: String,
/// time message was received
timestamp_received: DateTime<FixedOffset>,
/// the message
message: String,
/// notification instructions (based on settings)
notification: MessageNotification,
/// path to picture for the contact
picture: String,
},
/// A new contact has been created (imported, added, or contacted by)
ContactCreated {
conversation_id: i32,
remote_peer: Identity,
/// identity field
profile: ProfileIdentity,
/// conversation id
conversation_id: ConversationID,
/// contact id
contact: ContactIdentity,
/// name of group
nick: String,
/// connection status to group server
status: ConnectionState,
/// number of unread messages in group
unread: i32,
/// path to picture for group
picture: String,
/// path to default picture for group
default_picture: String,
/// total number of messages in group
num_messages: i32,
/// has the user accepted the group
accepted: bool,
/// ACL for the group
access_control_list: ACL,
/// is the group blocked
blocked: bool,
/// is the group syncing
loading: bool,
/// time of last message from the group
last_msg_time: DateTime<FixedOffset>,
},
/// A peer has changed state
PeerStateChange {
remote_peer: Identity,
/// identity field
profile: ProfileIdentity,
/// contact id
contact: ContactIdentity,
/// connection status to contact
connection_state: ConnectionState,
},
/// Notice from the network check plugin, a profile self check test by attempting to connecting to itself
NetworkStatus {
address: Identity,
/// profile the check was performed on
profile: ProfileIdentity, // it's technically a profile self check
/// error if there was one (can be empty)
error: String,
/// status of profile self connection check
status: NetworkCheckStatus,
},
/// Information from the ACN about a peer
ACNInfo {
handle: Identity,
/// identity field
profile: ProfileIdentity,
/// contact id
contact: ContactIdentity,
/// key of info
key: String,
/// data of info
data: String,
},
/// a profile attribute has been updated with a new value
UpdatedProfileAttribute {
/// identity field
profile: ProfileIdentity,
/// attribute key
key: String,
/// attribute new value
value: String,
},
/// emited to confirm ack of a message succeeded
IndexedAcknowledgement {
/// identity field
profile: ProfileIdentity,
/// conversation id
conversation_id: i32,
/// index of message acked
index: i32,
},
/// emited to signal failure to ack a message
IndexedFailure {
conversation_id: i32,
/// identity field
profile: ProfileIdentity,
/// conversation id
conversation_id: ConversationID,
/// index of failure of message to ack
index: i32,
handle: Identity,
/// contact id
contact: ContactIdentity,
/// error string
error: String
},
/// a peer has acked a message
PeerAcknowledgement {
/// identity field
profile: ProfileIdentity,
/// message id this is an ack to
event_id: String,
remote_peer: Identity,
/// contact id
contact: ContactIdentity,
/// conversation id
conversation_id: i32,
},
/// New message received on a group
NewMessageFromGroup {
conversation_id: i32,
/// identity field
profile: ProfileIdentity,
/// conversation id
conversation_id: ConversationID,
/// time of message
timestamp_sent: DateTime<FixedOffset>,
remote_peer: Identity,
/// contact id
contact: ContactIdentity,
/// message index
index: i32,
/// the message
message: String,
/// hash of the message
content_hash: String,
/// path to picture for sender
picture: String,
/// name of sender
nick: String,
/// notification policy (based on settings)
notification: MessageNotification,
},
/// notice a group has been created
GroupCreated {
conversation_id: i32,
group_id: String,
/// identity field
profile: ProfileIdentity,
/// conversation id
conversation_id: ConversationID,
/// group id
group_id: GroupID,
/// server the group is on
group_server: String,
/// name of group
group_name: String,
/// path to picture for group
picture: String,
/// Access Control List for group
access_control_list: ACL,
},
/// notice a new group exists
NewGroup {
conversation_id: i32,
group_id: String,
/// identity field
profile: ProfileIdentity,
/// conversation id
conversation_id: ConversationID,
/// group id
group_id: GroupID,
/// server the group is on
group_server: String,
/// invite string
group_invite: String,
/// group name
group_name: String,
/// path to group picture
picture: String,
/// Access Control List for group
access_control_list: ACL,
},
/// a server connection state has changed
ServerStateChange {
/// identity field
profile: ProfileIdentity,
/// server the group is on
group_server: String,
/// state of connection to server
state: ConnectionState
},
/// A getval call to a peer has returned a response
NewRetValMessageFromPeer {
remote_peer: Identity,
/// identity field
profile: ProfileIdentity,
/// conversation id
contact: ContactIdentity,
/// scope of the val
scope: String,
/// path of the val (zone.key)
path: String,
/// does the queried for value exist
exists: bool,
/// value
data: String,
/// optional filepath if there was a downloaded component
file_path: Option<String>
},
/// result of a call to share a file, the filekey and manifest to operate on it
ShareManifest {
filekey: String,
/// identity field
profile: ProfileIdentity,
/// filekey
filekey: FileKey,
/// serialized manifest of the share
serializedManifest: String,
},
/// Information on a peer fileshare has been received
ManifestSizeReceived {
filekey: String,
/// identity field
profile: ProfileIdentity,
/// filekey
filekey: FileKey,
/// size of manifest received for a share
manifest_size: i32,
handle: String,
/// contact id
contact: ContactIdentity,
},
/// An error has occured while trying to parse a peer sharefile
ManifestError {
handle: Identity,
filekey: String,
/// identity field
profile: ProfileIdentity,
/// contact id
contact: ContactIdentity,
/// filekey
filekey: FileKey,
},
/// A peer message about a shared file has been received
ManifestReceived {
handle: Identity,
filekey: String,
/// identity field
profile: ProfileIdentity,
/// contact id
contact: ContactIdentity,
/// filekey
filekey: FileKey,
/// serialized manifest
serialized_manifest: String,
},
/// a received manfiest has been saved
ManifestSaved {
handle: Identity,
filekey: String,
/// identity field
profile: ProfileIdentity,
/// contact id
contact: ContactIdentity,
/// filekey
filekey: FileKey,
/// serialized manifest
serialized_manifest: String,
/// temporary storage path for share download
temp_file: String,
/// contact suggested share file name
name_suggestion: String,
},
/// periodically emited status updates about an active download of a shared file
FileDownloadProgressUpdate {
filekey: String,
/// identity field
profile: ProfileIdentity,
/// filekey
filekey: FileKey,
/// progress of download of share in chunks
progress: i32,
/// size of share in chunks
filesize_in_chunks: i32,
/// contact suggested name for file
name_suggestion: String,
},
// FileDownloaded, ??
@ -333,7 +543,9 @@ pub enum Event {
/// Indicates an event was sent from libCwtch that we don't handle/didn't anticipate
/// still passing it on giving the user a chance to react, but should be reported so we can handle it properly
ErrUnhandled {
/// name of unhandled event
name: String,
/// map of key:val attributes of unhandled event
data: HashMap<String, String>,
},
}
@ -343,7 +555,7 @@ impl From<&CwtchEvent> for Event {
match cwtch_event.event_type.as_str() {
"CwtchStarted" => Event::CwtchStarted,
"NewPeer" => Event::NewPeer {
identity: cwtch_event.data["Identity"].clone(),
profile: cwtch_event.data["Identity"].clone().into(),
tag: cwtch_event.data["tag"].clone(),
created: cwtch_event.data["Created"] == "true",
name: cwtch_event.data["name"].clone(),
@ -354,8 +566,9 @@ impl From<&CwtchEvent> for Event {
server_json: cwtch_event.data["ServerList"].clone(),
},
"NewMessageFromPeer" => Event::NewMessageFromPeer {
conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2),
handle: cwtch_event.data["RemotePeer"].clone(),
profile: cwtch_event.data["ProfileOnion"].clone().into(),
conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2).into(),
contact: cwtch_event.data["RemotePeer"].clone().into(),
nick: cwtch_event.data["Nick"].clone(),
timestamp_received: DateTime::parse_from_rfc3339(cwtch_event.data["TimestampReceived"].as_str()).unwrap_or( DateTime::from(Utc::now())),
message: cwtch_event.data["Data"].clone(),
@ -367,8 +580,9 @@ impl From<&CwtchEvent> for Event {
error: cwtch_event.data["Error"].clone(),
},
"ContactCreated" => Event::ContactCreated {
conversation_id: cwtch_event.data["ConversationID"].clone().parse().unwrap_or(-2),
remote_peer: cwtch_event.data["RemotePeer"].clone(),
profile: cwtch_event.data["ProfileOnion"].clone().into(),
conversation_id: cwtch_event.data["ConversationID"].clone().parse().unwrap_or(-2).into(),
contact: cwtch_event.data["RemotePeer"].clone().into(),
unread: cwtch_event.data["unread"].clone().parse().unwrap_or(0),
picture: cwtch_event.data["picture"].clone(),
default_picture: cwtch_event.data["defaultPicture"].clone(),
@ -382,49 +596,55 @@ impl From<&CwtchEvent> for Event {
last_msg_time: DateTime::parse_from_rfc3339(cwtch_event.data["lastMsgTime"].as_str()).unwrap_or(DateTime::from(Utc::now())),
},
"PeerStateChange" => Event::PeerStateChange {
remote_peer: cwtch_event.data["RemotePeer"].clone(),
profile: cwtch_event.data["ProfileOnion"].clone().into(),
contact: cwtch_event.data["RemotePeer"].clone().into(),
connection_state: ConnectionState::from(cwtch_event.data["ConnectionState"].as_str()),
},
"UpdateGlobalSettings" => Event::UpdateGlobalSettings {
settings: serde_json::from_str(cwtch_event.data["Data"].as_str()).unwrap_or(HashMap::new()),
},
"PeerDeleted" => Event::PeerDeleted { identity: cwtch_event.data["Identity"].clone() },
"PeerDeleted" => Event::PeerDeleted { profile: cwtch_event.data["Identity"].clone().into() },
"ACNStatus" => Event::ACNStatus {
progress: cwtch_event.data["Progress"].parse().unwrap_or(0),
status: cwtch_event.data["Status"].clone(),
},
"ACNVersion" => Event::ACNVersion { verstion: cwtch_event.data["Data"].clone()},
"ACNVersion" => Event::ACNVersion { version: cwtch_event.data["Data"].clone()},
"NetworkError" => Event::NetworkStatus {
address: cwtch_event.data["Onion"].clone(),
profile: cwtch_event.data["Onion"].clone().into(),
error: cwtch_event.data["Error"].clone(),
status: NetworkCheckStatus::from(cwtch_event.data["Status"].clone())
},
"ACNInfo" => Event::ACNInfo {
handle: cwtch_event.data["Handle"].clone(),
profile: cwtch_event.data["ProfileOnion"].clone().into(),
contact: cwtch_event.data["Handle"].clone().into(),
key: cwtch_event.data["Key"].clone(),
data: cwtch_event.data["Data"].clone(),
},
"UpdatedProfileAttribute" => Event::UpdatedProfileAttribute {
profile: cwtch_event.data["ProfileOnion"].clone().into(),
key: cwtch_event.data["Key"].clone(),
value: cwtch_event.data["Data"].clone(),
},
"IndexedAcknowledgement" => Event::IndexedAcknowledgement {
profile: cwtch_event.data["ProfileOnion"].clone().into(),
conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-1),
index: cwtch_event.data["Index"].parse().unwrap_or(-1),
},
"IndexedAcknowledgement" => Event::IndexedFailure {
conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-1),
profile: cwtch_event.data["ProfileOnion"].clone().into(),
conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-1).into(),
index: cwtch_event.data["Index"].parse().unwrap_or(-1),
handle: cwtch_event.data["Handle"].clone(),
contact: cwtch_event.data["Handle"].clone().into(),
error: cwtch_event.data["Error"].clone(),
},
"ShareManifest" => Event::ShareManifest {
filekey: cwtch_event.data["FileKey"].clone(),
profile: cwtch_event.data["ProfileOnion"].clone().into(),
filekey: cwtch_event.data["FileKey"].clone().into(),
serializedManifest: cwtch_event.data["SerializedManifest"].clone(),
},
"NewServer" => Event::NewServer {
onion: cwtch_event.data["Onion"].clone(),
server: cwtch_event.data["Onion"].clone().into(),
server_bundle: cwtch_event.data["ServerBundle"].clone(),
description: cwtch_event.data["Description"].clone(),
storage_type: ServerStorageType::from(cwtch_event.data["StorageType"].clone()),
@ -432,11 +652,11 @@ impl From<&CwtchEvent> for Event {
running: cwtch_event.data["Running"].parse().unwrap_or(false),
},
"ServerIntentUpdate" => Event::ServerIntentUpdate {
identity: cwtch_event.data["Identity"].clone(),
server: cwtch_event.data["Identity"].clone().into(),
intent: ServerIntent::from(cwtch_event.data["Intent"].clone())
},
"ServerDeleted" => Event::ServerDeleted {
identity: cwtch_event.data["Identity"].clone(),
server: cwtch_event.data["Identity"].clone().into(),
success: cwtch_event.data["Status"].clone() == "success",
error: match cwtch_event.data.get("Error") {
Some(e) => Some(e.clone()),
@ -444,64 +664,73 @@ impl From<&CwtchEvent> for Event {
}
},
"ServerStatsUpdate" => Event::ServerStatsUpdate {
identity: cwtch_event.data["Identity"].clone(),
server: cwtch_event.data["Identity"].clone().into(),
total_messages: cwtch_event.data["TotalMessages"].parse().unwrap_or(0),
connections: cwtch_event.data["Connections"].parse().unwrap_or(0),
},
"ManifestSizeReceived" => Event::ManifestSizeReceived {
filekey: cwtch_event.data["FileKey"].clone(),
profile: cwtch_event.data["ProfileOnion"].clone().into(),
filekey: cwtch_event.data["FileKey"].clone().into(),
manifest_size: cwtch_event.data["ManifestSize"].parse().unwrap_or(0),
handle: cwtch_event.data["Handle"].clone(),
contact: cwtch_event.data["Handle"].clone().into(),
},
"ManifestError" => Event::ManifestError {
handle: cwtch_event.data["Handle"].clone(),
filekey: cwtch_event.data["FileKey"].clone(),
profile: cwtch_event.data["ProfileOnion"].clone().into(),
contact: cwtch_event.data["Handle"].clone().into(),
filekey: cwtch_event.data["FileKey"].clone().into(),
},
"ManifestReceived" => Event::ManifestReceived {
handle: cwtch_event.data["Handle"].clone(),
filekey: cwtch_event.data["FileKey"].clone(),
profile: cwtch_event.data["ProfileOnion"].clone().into(),
contact: cwtch_event.data["Handle"].clone().into(),
filekey: cwtch_event.data["FileKey"].clone().into(),
serialized_manifest: cwtch_event.data["SerializedManifest"].clone(),
},
"ManifestSaved" => Event::ManifestSaved {
handle: cwtch_event.data["Handle"].clone(),
filekey: cwtch_event.data["FileKey"].clone(),
profile: cwtch_event.data["ProfileOnion"].clone().into(),
contact: cwtch_event.data["Handle"].clone().into(),
filekey: cwtch_event.data["FileKey"].clone().into(),
serialized_manifest: cwtch_event.data["SerializedManifest"].clone(),
temp_file: cwtch_event.data["TempFile"].clone(),
name_suggestion: cwtch_event.data["NameSuggestion"].clone(),
},
"FileDownloadProgressUpdate" => Event::FileDownloadProgressUpdate {
filekey: cwtch_event.data["FileKey"].clone(),
profile: cwtch_event.data["ProfileOnion"].clone().into(),
filekey: cwtch_event.data["FileKey"].clone().into(),
progress: cwtch_event.data["Progress"].parse().unwrap_or(0),
filesize_in_chunks: cwtch_event.data["FileSizeInChunks"].parse().unwrap_or(0),
name_suggestion: cwtch_event.data["NameSuggestion"].clone(),
},
"PeerAcknowledgement" => Event::PeerAcknowledgement {
profile: cwtch_event.data["ProfileOnion"].clone().into(),
event_id: cwtch_event.data["EventId"].clone(),
remote_peer: cwtch_event.data["RemotePeer"].clone(),
contact: cwtch_event.data["RemotePeer"].clone().into(),
conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(0)
},
"NewMessageFromGroup" => Event::NewMessageFromGroup {
profile: cwtch_event.data["ProfileOnion"].clone().into(),
timestamp_sent: DateTime::parse_from_rfc3339(cwtch_event.data["TimestampSent"].as_str()).unwrap_or( DateTime::from(Utc::now())),
index: cwtch_event.data["RemotePeer"].parse().unwrap_or(-1),
content_hash: cwtch_event.data["ContentHash"].clone(),
conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2),
remote_peer: cwtch_event.data["RemotePeer"].clone(),
conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2).into(),
contact: cwtch_event.data["RemotePeer"].clone().into(),
nick: cwtch_event.data["Nick"].clone(),
message: cwtch_event.data["Data"].clone(),
notification: MessageNotification::from(cwtch_event.data["notification"].clone()),
picture: cwtch_event.data["Picture"].clone(),
},
"GroupCreated" => Event::GroupCreated {
conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2),
group_id: cwtch_event.data["GroupID"].clone(),
profile: cwtch_event.data["ProfileOnion"].clone().into(),
conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2).into(),
group_id: cwtch_event.data["GroupID"].clone().into(),
group_server: cwtch_event.data["GroupServer"].clone(),
group_name: cwtch_event.data["GroupName"].clone(),
picture: cwtch_event.data["picture"].clone(),
access_control_list: serde_json::from_str(cwtch_event.data["accessControlList"].as_str()).unwrap_or(ACL::new()),
},
"NewGroup" => Event::NewGroup {
conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2),
group_id: cwtch_event.data["GroupID"].clone(),
profile: cwtch_event.data["ProfileOnion"].clone().into(),
conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2).into(),
group_id: cwtch_event.data["GroupID"].clone().into(),
group_server: cwtch_event.data["GroupServer"].clone(),
group_invite: cwtch_event.data["GroupInvite"].clone(),
group_name: cwtch_event.data["GroupName"].clone(),
@ -509,11 +738,13 @@ impl From<&CwtchEvent> for Event {
access_control_list: serde_json::from_str(cwtch_event.data["accessControlList"].as_str()).unwrap_or(ACL::new()),
},
"ServerStateChange" => Event::ServerStateChange {
profile: cwtch_event.data["ProfileOnion"].clone().into(),
group_server: cwtch_event.data["GroupServer"].clone(),
state: ConnectionState::from(cwtch_event.data["ConnectionState"].as_str()),
},
"NewRetValMessageFromPeer" => Event::NewRetValMessageFromPeer {
remote_peer: cwtch_event.data["RemotePeer"].clone(),
profile: cwtch_event.data["ProfileOnion"].clone().into(),
contact: cwtch_event.data["RemotePeer"].clone().into(),
scope: cwtch_event.data["Scope"].clone(),
path: cwtch_event.data["Path"].clone(),
exists: cwtch_event.data["Exists"].parse().unwrap_or(false),

View File

@ -10,6 +10,7 @@ mod cwtchlib_go;
/// Basic structs using data from Cwtch and for deserializing JSON and serializing to JSON to communicate with Cwtch
pub mod structs;
/// Additional structs for advnaced event handling and converstion helpers
pub mod event;
/// Error type for Cwtch lib related errors, intended for use with Result