Add mastodon_async_entities

This patch move the entities module to a helper-crate.

With this, we give the user the opportunity to use only the entities
types in their codebase, if need be.
One scenario where this is required came up in

    https://github.com/dscottboggs/mastodon-async/issues/38

TL;DR is: A user needed to be able to pass types like `Status` from a
backend part of an application to a frontend which was compiled to WASM.
Because mastodon_async depends on tokio, which does not compile to WASM
(at least not with the features required by mastodon_async).

One option would have been to provide types in the application code
which can be constructed from mastodon_asyncs entity types. This would
lead to _a lot_ of code duplication (over several projects still,... but
that's rather undesireable anyways).

The solution mastodon_async offers with this patch is a helper-crate
which only contains the entity types: mastodon_async_entities.

mastodon_async publicly exports the whole mastodon_async_entities crate,
so users do not have to depend on the latter directly.

In addition to the `entities` module from mastodon_async, also the
`Visibility` type had to be moved.

`mastodon_async_entities` also got an own `Error` type, which
`mastodon_async::Error` can of course wrap.

Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
Suggested-by: D. Scott Boggs <scott@tams.tech>
This commit is contained in:
Matthias Beyer 2022-12-30 10:41:37 +01:00 committed by Scott Boggs
parent 02de9f5d32
commit af3facfbf0
25 changed files with 202 additions and 158 deletions

View File

@ -1,3 +1,11 @@
[workspace]
resolver = "2"
members = [
".",
"entities",
]
[package] [package]
name = "mastodon-async" name = "mastodon-async"
version = "1.0.3" version = "1.0.3"
@ -13,6 +21,10 @@ repository = "https://github.com/dscottboggs/mastodon-async.git"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["all"] features = ["all"]
[dependencies.mastodon-async-entities]
path = "./entities"
version = "1.0.3"
[dependencies] [dependencies]
futures = "0.3.25" futures = "0.3.25"
doc-comment = "0.3" doc-comment = "0.3"

13
entities/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "mastodon-async-entities"
version = "1.0.3"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
futures = "0.3.25"
log = { version = "0.4", features = ["kv_unstable", "serde", "std", "kv_unstable_serde", "kv_unstable_std"] }
serde = { version = "1", features = ["derive"] }
thiserror = "1"
time = { version = "0.3", features = ["parsing", "serde", "formatting"] }

View File

@ -1,6 +1,5 @@
//! A module containing everything relating to a account returned from the api. //! A module containing everything relating to a account returned from the api.
use crate::status_builder;
use serde::{ use serde::{
de::{self, Deserializer, Unexpected}, de::{self, Deserializer, Unexpected},
Deserialize, Serialize, Deserialize, Serialize,
@ -65,7 +64,7 @@ pub struct MetadataField {
} }
impl MetadataField { impl MetadataField {
pub(crate) fn new(name: &str, value: &str) -> MetadataField { pub fn new(name: &str, value: &str) -> MetadataField {
MetadataField { MetadataField {
name: name.into(), name: name.into(),
value: value.into(), value: value.into(),
@ -76,7 +75,7 @@ impl MetadataField {
/// An extra object given from `verify_credentials` giving defaults about a user /// An extra object given from `verify_credentials` giving defaults about a user
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct Source { pub struct Source {
privacy: Option<status_builder::Visibility>, privacy: Option<crate::visibility::Visibility>,
#[serde(deserialize_with = "string_or_bool")] #[serde(deserialize_with = "string_or_bool")]
sensitive: bool, sensitive: bool,
note: Option<String>, note: Option<String>,
@ -109,33 +108,33 @@ fn string_or_bool<'de, D: Deserializer<'de>>(val: D) -> ::std::result::Result<bo
} }
#[derive(Debug, Default, Clone, Serialize, PartialEq, Eq)] #[derive(Debug, Default, Clone, Serialize, PartialEq, Eq)]
pub(crate) struct UpdateSource { pub struct UpdateSource {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub(crate) privacy: Option<status_builder::Visibility>, pub privacy: Option<crate::visibility::Visibility>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub(crate) sensitive: Option<bool>, pub sensitive: Option<bool>,
} }
#[derive(Debug, Default, Serialize, PartialEq, Eq)] #[derive(Debug, Default, Serialize, PartialEq, Eq)]
pub(crate) struct Credentials { pub struct Credentials {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub(crate) display_name: Option<String>, pub display_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub(crate) note: Option<String>, pub note: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub(crate) avatar: Option<PathBuf>, pub avatar: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub(crate) header: Option<PathBuf>, pub header: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub(crate) source: Option<UpdateSource>, pub source: Option<UpdateSource>,
#[serde(serialize_with = "fields_attributes_ser::ser")] #[serde(serialize_with = "fields_attributes_ser::ser")]
pub(crate) fields_attributes: Vec<MetadataField>, pub fields_attributes: Vec<MetadataField>,
} }
mod fields_attributes_ser { mod fields_attributes_ser {
use super::*; use super::*;
use serde::ser::{SerializeMap, Serializer}; use serde::ser::{SerializeMap, Serializer};
pub(crate) fn ser<S>(attrs: &Vec<MetadataField>, serializer: S) -> Result<S::Ok, S::Error> pub fn ser<S>(attrs: &Vec<MetadataField>, serializer: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {

6
entities/src/error.rs Normal file
View File

@ -0,0 +1,6 @@
/// Error type
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("unrecognized visibility '{invalid}'")]
VisibilityParsingError { invalid: String },
}

View File

@ -1,4 +1,4 @@
use crate::entities::{notification::Notification, status::Status}; use crate::{notification::Notification, status::Status};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]

66
entities/src/lib.rs Normal file
View File

@ -0,0 +1,66 @@
use serde::Deserialize;
use serde::Serialize;
/// Error types for this crate
pub mod error;
/// Data structures for ser/de of account-related resources
pub mod account;
/// Data structures for ser/de of attachment-related resources
pub mod attachment;
/// Data structures for ser/de of card-related resources
pub mod card;
/// Data structures for ser/de of contetx-related resources
pub mod context;
/// Data structures for ser/de of streaming events
pub mod event;
/// Data structures for ser/de of filter-related resources
pub mod filter;
/// Data structures for ser/de of instance-related resources
pub mod instance;
/// Data structures for ser/de of list-related resources
pub mod list;
/// Data structures for ser/de of mention-related resources
pub mod mention;
/// Data structures for ser/de of notification-related resources
pub mod notification;
/// Data structures for ser/de of push-subscription-related resources
pub mod push;
/// Data structures for ser/de of relationship-related resources
pub mod relationship;
/// Data structures for ser/de of report-related resources
pub mod report;
/// Data structures for ser/de of search-related resources
pub mod search_result;
/// Data structures for ser/de of status-related resources
pub mod status;
/// Data structure for ser/de visibility
pub mod visibility;
/// An empty JSON object.
#[derive(Deserialize, Serialize, Debug, Copy, Clone, PartialEq, Eq)]
pub struct Empty {}
/// The purpose of this module is to alleviate imports of many common
/// structs by adding a glob import to the top of mastodon heavy
/// modules:
pub mod prelude {
pub use super::{
account::{Account, Source},
attachment::{Attachment, MediaType},
card::Card,
context::Context,
event::Event,
filter::{Filter, FilterContext},
instance::*,
list::List,
mention::Mention,
notification::Notification,
push::Subscription,
relationship::Relationship,
report::Report,
search_result::SearchResult,
status::{Application, Emoji, Status},
Empty,
};
}

View File

@ -26,48 +26,48 @@ pub struct Subscription {
pub alerts: Option<Alerts>, pub alerts: Option<Alerts>,
} }
pub(crate) mod add_subscription { pub mod add_subscription {
use serde::Serialize;
use super::Alerts;
#[derive(Debug, Clone, PartialEq, Serialize, Default)]
pub(crate) struct Form {
pub(crate) subscription: Subscription,
pub(crate) data: Option<Data>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
pub(crate) struct Subscription {
pub(crate) endpoint: String,
pub(crate) keys: Keys,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
pub(crate) struct Keys {
pub(crate) p256dh: String,
pub(crate) auth: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
pub(crate) struct Data {
pub(crate) alerts: Option<Alerts>,
}
}
pub(crate) mod update_data {
use serde::Serialize; use serde::Serialize;
use super::Alerts; use super::Alerts;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
pub(crate) struct Data { pub struct Form {
pub(crate) alerts: Option<Alerts>, pub subscription: Subscription,
pub data: Option<Data>,
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
pub(crate) struct Form { pub struct Subscription {
pub(crate) id: String, pub endpoint: String,
pub(crate) data: Data, pub keys: Keys,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
pub struct Keys {
pub p256dh: String,
pub auth: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
pub struct Data {
pub alerts: Option<Alerts>,
}
}
pub mod update_data {
use serde::Serialize;
use super::Alerts;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
pub struct Data {
pub alerts: Option<Alerts>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
pub struct Form {
pub id: String,
pub data: Data,
} }
} }

View File

@ -1,7 +1,7 @@
//! Module containing all info relating to a status. //! Module containing all info relating to a status.
use super::prelude::*; use super::prelude::*;
use crate::{entities::card::Card, status_builder::Visibility}; use crate::{card::Card, visibility::Visibility};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::{serde::iso8601, OffsetDateTime}; use time::{serde::iso8601, OffsetDateTime};

View File

@ -0,0 +1,38 @@
use serde::Deserialize;
use serde::Serialize;
/// The visibility of a status.
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum Visibility {
/// A Direct message to a user
Direct,
/// Only available to followers
Private,
/// Not shown in public timelines
Unlisted,
/// Posted to public timelines
Public,
}
impl Default for Visibility {
fn default() -> Self {
Visibility::Public
}
}
impl std::str::FromStr for Visibility {
type Err = crate::error::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"direct" => Ok(Visibility::Direct),
"private" => Ok(Visibility::Private),
"unlisted" => Ok(Visibility::Unlisted),
"public" => Ok(Visibility::Public),
invalid => Err(crate::error::Error::VisibilityParsingError {
invalid: invalid.to_string(),
}),
}
}
}

View File

@ -1,61 +1,3 @@
use serde::Deserialize;
/// Data structures for ser/de of account-related resources
pub mod account;
/// Data structures for ser/de of attachment-related resources
pub mod attachment;
/// Data structures for ser/de of card-related resources
pub mod card;
/// Data structures for ser/de of contetx-related resources
pub mod context;
/// Data structures for ser/de of streaming events
pub mod event;
/// Data structures for ser/de of filter-related resources
pub mod filter;
/// Data structures for ser/de of instance-related resources
pub mod instance;
pub(crate) mod itemsiter; pub(crate) mod itemsiter;
/// Data structures for ser/de of list-related resources
pub mod list;
/// Data structures for ser/de of mention-related resources
pub mod mention;
/// Data structures for ser/de of notification-related resources
pub mod notification;
/// Data structures for ser/de of push-subscription-related resources
pub mod push;
/// Data structures for ser/de of relationship-related resources
pub mod relationship;
/// Data structures for ser/de of report-related resources
pub mod report;
/// Data structures for ser/de of search-related resources
pub mod search_result;
/// Data structures for ser/de of status-related resources
pub mod status;
/// An empty JSON object. pub use mastodon_async_entities::*;
#[derive(Deserialize, Serialize, Debug, Copy, Clone, PartialEq, Eq)]
pub struct Empty {}
/// The purpose of this module is to alleviate imports of many common
/// structs by adding a glob import to the top of mastodon heavy
/// modules:
pub mod prelude {
pub use super::{
account::{Account, Source},
attachment::{Attachment, MediaType},
card::Card,
context::Context,
event::Event,
filter::{Filter, FilterContext},
instance::*,
list::List,
mention::Mention,
notification::Notification,
push::Subscription,
relationship::Relationship,
report::Report,
search_result::SearchResult,
status::{Application, Emoji, Status},
Empty,
};
}

View File

@ -99,6 +99,9 @@ pub enum Error {
/// larger than the system's usize allows. /// larger than the system's usize allows.
#[error("integer didn't fit in the target size")] #[error("integer didn't fit in the target size")]
IntConversion(#[from] TryFromIntError), IntConversion(#[from] TryFromIntError),
/// Error from mastodon-async-entities
#[error(transparent)]
Entities(#[from] mastodon_async_entities::error::Error),
/// Other errors /// Other errors
#[error("other error: {0:?}")] #[error("other error: {0:?}")]
Other(String), Other(String),

View File

@ -92,11 +92,12 @@ pub use errors::{ApiError, Error, Result};
pub use isolang::Language; pub use isolang::Language;
pub use mastodon::{Mastodon, MastodonUnauthenticated}; pub use mastodon::{Mastodon, MastodonUnauthenticated};
// pub use mastodon_client::{MastodonClient, MastodonUnauthenticated}; // pub use mastodon_client::{MastodonClient, MastodonUnauthenticated};
pub use mastodon_async_entities::visibility::Visibility;
pub use registration::Registration; pub use registration::Registration;
pub use requests::{ pub use requests::{
AddFilterRequest, AddPushRequest, StatusesRequest, UpdateCredsRequest, UpdatePushRequest, AddFilterRequest, AddPushRequest, StatusesRequest, UpdateCredsRequest, UpdatePushRequest,
}; };
pub use status_builder::{NewStatus, StatusBuilder, Visibility}; pub use status_builder::{NewStatus, StatusBuilder};
/// Registering your App /// Registering your App
pub mod apps; pub mod apps;
@ -126,7 +127,6 @@ mod macros;
pub mod prelude { pub mod prelude {
pub use crate::{ pub use crate::{
scopes::Scopes, scopes::Scopes,
status_builder::Visibility,
Data, Data,
Mastodon, Mastodon,
// MastodonClient, // MastodonClient,
@ -134,6 +134,7 @@ pub mod prelude {
Registration, Registration,
StatusBuilder, StatusBuilder,
StatusesRequest, StatusesRequest,
Visibility,
}; };
} }
/// The mastodon client /// The mastodon client

View File

@ -6,15 +6,15 @@ use std::{
use crate::{ use crate::{
entities::account::{Credentials, MetadataField, UpdateSource}, entities::account::{Credentials, MetadataField, UpdateSource},
errors::Result, errors::Result,
status_builder,
}; };
use mastodon_async_entities::visibility::Visibility;
/// Builder to pass to the Mastodon::update_credentials method /// Builder to pass to the Mastodon::update_credentials method
/// ///
/// // Example /// // Example
/// ///
/// ```no_run /// ```no_run
/// use mastodon_async::{prelude::*, status_builder::Visibility, UpdateCredsRequest}; /// use mastodon_async::{prelude::*, entities::visibility::Visibility, UpdateCredsRequest};
/// ///
/// let data = Data::default(); /// let data = Data::default();
/// let client = Mastodon::from(data); /// let client = Mastodon::from(data);
@ -35,7 +35,7 @@ pub struct UpdateCredsRequest {
field_attributes: Vec<MetadataField>, field_attributes: Vec<MetadataField>,
// UpdateSource fields // UpdateSource fields
privacy: Option<status_builder::Visibility>, privacy: Option<Visibility>,
sensitive: Option<bool>, sensitive: Option<bool>,
} }
@ -126,13 +126,13 @@ impl UpdateCredsRequest {
/// // Example /// // Example
/// ///
/// ``` /// ```
/// use mastodon_async::{status_builder::Visibility, UpdateCredsRequest}; /// use mastodon_async::{entities::visibility::Visibility, UpdateCredsRequest};
/// ///
/// let mut builder = UpdateCredsRequest::new(); /// let mut builder = UpdateCredsRequest::new();
/// ///
/// builder.privacy(Visibility::Public); /// builder.privacy(Visibility::Public);
/// ``` /// ```
pub fn privacy(&mut self, privacy: status_builder::Visibility) -> &mut Self { pub fn privacy(&mut self, privacy: Visibility) -> &mut Self {
self.privacy = Some(privacy); self.privacy = Some(privacy);
self self
} }
@ -188,7 +188,7 @@ impl UpdateCredsRequest {
mod tests { mod tests {
use super::*; use super::*;
use crate::entities::account::{Credentials, MetadataField, UpdateSource}; use crate::entities::account::{Credentials, MetadataField, UpdateSource};
use status_builder::Visibility; use mastodon_async_entities::visibility::Visibility;
#[test] #[test]
fn test_update_creds_request_new() { fn test_update_creds_request_new() {

View File

@ -1,9 +1,7 @@
use std::str::FromStr;
use isolang::Language; use isolang::Language;
use serde::{Deserialize, Serialize}; use serde::Serialize;
use crate::format_err; use mastodon_async_entities::visibility::Visibility;
/// A builder pattern struct for constructing a status. /// A builder pattern struct for constructing a status.
/// ///
@ -37,7 +35,7 @@ impl StatusBuilder {
/// // Example /// // Example
/// ///
/// ```rust,no_run /// ```rust,no_run
/// use mastodon_async::{status_builder::Visibility, prelude::*}; /// use mastodon_async::{entities::visibility::Visibility, prelude::*};
/// ///
/// let data = Data::default(); /// let data = Data::default();
/// let client = Mastodon::from(data); /// let client = Mastodon::from(data);
@ -150,7 +148,7 @@ impl StatusBuilder {
/// // Example /// // Example
/// ///
/// ```rust /// ```rust
/// use mastodon_async::{prelude::*, status_builder::Visibility}; /// use mastodon_async::{prelude::*, entities::visibility::Visibility};
/// let status = StatusBuilder::new() /// let status = StatusBuilder::new()
/// .status("awooooooo") /// .status("awooooooo")
/// .visibility(Visibility::Public) /// .visibility(Visibility::Public)
@ -242,40 +240,6 @@ pub struct NewStatus {
content_type: Option<String>, content_type: Option<String>,
} }
/// The visibility of a status.
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum Visibility {
/// A Direct message to a user
Direct,
/// Only available to followers
Private,
/// Not shown in public timelines
Unlisted,
/// Posted to public timelines
Public,
}
impl Default for Visibility {
fn default() -> Self {
Visibility::Public
}
}
impl FromStr for Visibility {
type Err = crate::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"direct" => Ok(Visibility::Direct),
"private" => Ok(Visibility::Private),
"unlisted" => Ok(Visibility::Unlisted),
"public" => Ok(Visibility::Public),
invalid => Err(format_err!("unrecognized visibility '{invalid}'")),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;