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]
name = "mastodon-async"
version = "1.0.3"
@ -13,6 +21,10 @@ repository = "https://github.com/dscottboggs/mastodon-async.git"
[package.metadata.docs.rs]
features = ["all"]
[dependencies.mastodon-async-entities]
path = "./entities"
version = "1.0.3"
[dependencies]
futures = "0.3.25"
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.
use crate::status_builder;
use serde::{
de::{self, Deserializer, Unexpected},
Deserialize, Serialize,
@ -65,7 +64,7 @@ pub struct MetadataField {
}
impl MetadataField {
pub(crate) fn new(name: &str, value: &str) -> MetadataField {
pub fn new(name: &str, value: &str) -> MetadataField {
MetadataField {
name: name.into(),
value: value.into(),
@ -76,7 +75,7 @@ impl MetadataField {
/// An extra object given from `verify_credentials` giving defaults about a user
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct Source {
privacy: Option<status_builder::Visibility>,
privacy: Option<crate::visibility::Visibility>,
#[serde(deserialize_with = "string_or_bool")]
sensitive: bool,
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)]
pub(crate) struct UpdateSource {
pub struct UpdateSource {
#[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")]
pub(crate) sensitive: Option<bool>,
pub sensitive: Option<bool>,
}
#[derive(Debug, Default, Serialize, PartialEq, Eq)]
pub(crate) struct Credentials {
pub struct Credentials {
#[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")]
pub(crate) note: Option<String>,
pub note: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) avatar: Option<PathBuf>,
pub avatar: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) header: Option<PathBuf>,
pub header: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) source: Option<UpdateSource>,
pub source: Option<UpdateSource>,
#[serde(serialize_with = "fields_attributes_ser::ser")]
pub(crate) fields_attributes: Vec<MetadataField>,
pub fields_attributes: Vec<MetadataField>,
}
mod fields_attributes_ser {
use super::*;
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
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};
#[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(crate) 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 {
pub mod add_subscription {
use serde::Serialize;
use super::Alerts;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
pub(crate) struct Data {
pub(crate) alerts: Option<Alerts>,
pub struct Form {
pub subscription: Subscription,
pub data: Option<Data>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
pub(crate) struct Form {
pub(crate) id: String,
pub(crate) data: Data,
pub struct Subscription {
pub endpoint: String,
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.
use super::prelude::*;
use crate::{entities::card::Card, status_builder::Visibility};
use crate::{card::Card, visibility::Visibility};
use serde::{Deserialize, Serialize};
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;
/// 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.
#[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,
};
}
pub use mastodon_async_entities::*;

View File

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

View File

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

View File

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

View File

@ -1,9 +1,7 @@
use std::str::FromStr;
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.
///
@ -37,7 +35,7 @@ impl StatusBuilder {
/// // Example
///
/// ```rust,no_run
/// use mastodon_async::{status_builder::Visibility, prelude::*};
/// use mastodon_async::{entities::visibility::Visibility, prelude::*};
///
/// let data = Data::default();
/// let client = Mastodon::from(data);
@ -150,7 +148,7 @@ impl StatusBuilder {
/// // Example
///
/// ```rust
/// use mastodon_async::{prelude::*, status_builder::Visibility};
/// use mastodon_async::{prelude::*, entities::visibility::Visibility};
/// let status = StatusBuilder::new()
/// .status("awooooooo")
/// .visibility(Visibility::Public)
@ -242,40 +240,6 @@ pub struct NewStatus {
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)]
mod tests {
use super::*;