Simplify proc_macro tests

… by moving them from ruma-events-macros to ruma-events and updating the
macros to work in different contexts (without items like FromRaw being
available at the calling crate's root)
This commit is contained in:
Jonas Platte 2020-03-17 13:10:40 +01:00
parent 016659a99c
commit 0c6dd0ba32
No known key found for this signature in database
GPG Key ID: CC154DE0E30B7C67
6 changed files with 47 additions and 310 deletions

View File

@ -21,11 +21,11 @@ pub fn expand_from_raw(input: DeriveInput) -> syn::Result<TokenStream> {
if field_ident == "content" {
quote_spanned! {field_span=>
content: crate::FromRaw::from_raw(raw.content),
content: ::ruma_events::FromRaw::from_raw(raw.content),
}
} else if field_ident == "prev_content" {
quote_spanned! {field_span=>
prev_content: raw.prev_content.map(crate::FromRaw::from_raw),
prev_content: raw.prev_content.map(::ruma_events::FromRaw::from_raw),
}
} else {
quote_spanned! {field_span=>
@ -35,7 +35,7 @@ pub fn expand_from_raw(input: DeriveInput) -> syn::Result<TokenStream> {
});
Ok(quote! {
impl crate::FromRaw for #ident {
impl ::ruma_events::FromRaw for #ident {
type Raw = raw::#ident;
fn from_raw(raw: raw::#ident) -> Self {

View File

@ -92,7 +92,7 @@ impl ToTokens for RumaEvent {
let event_type = if self.is_custom {
quote! {
crate::EventType::Custom(self.event_type.clone())
::ruma_events::EventType::Custom(self.event_type.clone())
}
} else {
let event_type = &self.event_type;
@ -189,7 +189,7 @@ impl ToTokens for RumaEvent {
};
let import_event_in_serialize_impl = quote! {
use crate::Event as _;
use ::ruma_events::Event as _;
};
(
@ -214,7 +214,7 @@ impl ToTokens for RumaEvent {
let impl_room_event = match self.kind {
EventKind::RoomEvent | EventKind::StateEvent => {
quote! {
impl crate::RoomEvent for #name {
impl ::ruma_events::RoomEvent for #name {
/// The unique identifier for the event.
fn event_id(&self) -> &ruma_identifiers::EventId {
&self.event_id
@ -251,7 +251,7 @@ impl ToTokens for RumaEvent {
let impl_state_event = if self.kind == EventKind::StateEvent {
quote! {
impl crate::StateEvent for #name {
impl ::ruma_events::StateEvent for #name {
/// The previous content for this state key, if any.
fn prev_content(&self) -> Option<&Self::Content> {
self.prev_content.as_ref()
@ -284,7 +284,7 @@ impl ToTokens for RumaEvent {
}
quote! {
impl crate::FromRaw for #content_name {
impl ::ruma_events::FromRaw for #content_name {
type Raw = raw::#content_name;
fn from_raw(
@ -337,7 +337,7 @@ impl ToTokens for RumaEvent {
}
}
impl crate::Event for #name {
impl ::ruma_events::Event for #name {
/// The type of this event's `content` field.
type Content = #content_name;
@ -347,7 +347,7 @@ impl ToTokens for RumaEvent {
}
/// The type of the event.
fn event_type(&self) -> crate::EventType {
fn event_type(&self) -> ::ruma_events::EventType {
#event_type
}
}

View File

@ -109,7 +109,7 @@ impl Parse for RumaEventInput {
let mut punctuated = Punctuated::new();
punctuated.push(PathSegment {
ident: Ident::new("crate", Span::call_site()),
ident: Ident::new("ruma_events", Span::call_site()),
arguments: PathArguments::None,
});
punctuated.push(PathSegment {
@ -119,7 +119,7 @@ impl Parse for RumaEventInput {
punctuated.push(variant.clone());
event_type = Some(Path {
leading_colon: None,
leading_colon: Some(Default::default()),
segments: punctuated,
});
}

View File

@ -135,7 +135,12 @@ mod macros;
mod algorithm;
mod event_type;
mod from_raw;
mod util;
#[doc(hidden)] // only public for external tests
pub mod util;
// Hack to allow both ruma-events itself and external crates (or tests) to use procedural macros
// that expect `ruma_events` to exist in the prelude.
extern crate self as ruma_events;
pub mod call;
/// Enums for heterogeneous collections of events.

View File

@ -1,7 +1,12 @@
use serde::de::{Deserialize, DeserializeOwned, IntoDeserializer};
use std::fmt::Debug;
use serde::{
de::{Deserialize, DeserializeOwned, IntoDeserializer},
Serialize,
};
use serde_json::Value;
use crate::TryFromRaw;
use crate::{EventResult, TryFromRaw};
pub fn try_convert_variant<Enum: TryFromRaw, Content: TryFromRaw>(
variant: fn(Content) -> Enum,
@ -107,35 +112,25 @@ pub fn default_true() -> bool {
}
#[cfg(test)]
mod test_util {
use std::fmt::Debug;
use serde::{de::DeserializeOwned, Serialize};
use crate::{EventResult, TryFromRaw};
pub fn serde_json_eq<T>(de: T, se: serde_json::Value)
where
T: Clone + Debug + PartialEq + Serialize + DeserializeOwned,
{
assert_eq!(se, serde_json::to_value(de.clone()).unwrap());
assert_eq!(de, serde_json::from_value(se).unwrap());
}
pub fn serde_json_eq_try_from_raw<T>(de: T, se: serde_json::Value)
where
T: Clone + Debug + PartialEq + Serialize + TryFromRaw,
{
assert_eq!(se, serde_json::to_value(de.clone()).unwrap());
assert_eq!(
de,
serde_json::from_value::<EventResult<_>>(se)
.unwrap()
.into_result()
.unwrap()
);
}
pub fn serde_json_eq<T>(de: T, se: serde_json::Value)
where
T: Clone + Debug + PartialEq + Serialize + DeserializeOwned,
{
assert_eq!(se, serde_json::to_value(de.clone()).unwrap());
assert_eq!(de, serde_json::from_value(se).unwrap());
}
#[cfg(test)]
pub use test_util::*;
// This would be #[cfg(test)] if it wasn't used from external tests
pub fn serde_json_eq_try_from_raw<T>(de: T, se: serde_json::Value)
where
T: Clone + Debug + PartialEq + Serialize + TryFromRaw,
{
assert_eq!(se, serde_json::to_value(de.clone()).unwrap());
assert_eq!(
de,
serde_json::from_value::<EventResult<_>>(se)
.unwrap()
.into_result()
.unwrap()
);
}

View File

@ -1,274 +1,11 @@
use std::{
borrow::Cow,
collections::HashMap,
convert::{Infallible, TryFrom},
fmt::{Debug, Display, Formatter, Result as FmtResult},
};
use std::{collections::HashMap, convert::TryFrom};
use js_int::UInt;
use ruma_events::util::serde_json_eq_try_from_raw;
use ruma_events_macros::ruma_event;
use ruma_identifiers::{EventId, RoomAliasId, RoomId, UserId};
use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize};
use serde_json::{json, Value};
/// The type of an event.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
// Cow<str> because deserialization sometimes needs to copy to unescape things
#[serde(from = "Cow<'_, str>", into = "String")]
pub enum EventType {
/// m.direct
Direct,
/// m.room.aliases
RoomAliases,
/// m.room.redaction
RoomRedaction,
/// Any event that is not part of the specification.
Custom(String),
}
impl Display for EventType {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let event_type_str = match *self {
EventType::Direct => "m.direct",
EventType::RoomAliases => "m.room.aliases",
EventType::RoomRedaction => "m.room.redaction",
EventType::Custom(ref event_type) => event_type,
};
write!(f, "{}", event_type_str)
}
}
impl<'a> From<Cow<'a, str>> for EventType {
fn from(s: Cow<'a, str>) -> EventType {
match &s as &str {
"m.direct" => EventType::Direct,
"m.room.aliases" => EventType::RoomAliases,
"m.room.redaction" => EventType::RoomRedaction,
_ => EventType::Custom(s.into_owned()),
}
}
}
impl From<&str> for EventType {
fn from(s: &str) -> EventType {
EventType::from(Cow::Borrowed(s))
}
}
impl From<EventType> for String {
fn from(event_type: EventType) -> String {
event_type.to_string()
}
}
/// The result of deserializing an event, which may or may not be valid.
#[derive(Debug)]
pub enum EventResult<T: TryFromRaw> {
/// `T` deserialized and validated successfully.
Ok(T),
/// `T` deserialized but was invalid.
///
/// `InvalidEvent` contains the original input.
Err(InvalidEvent),
}
impl<T: TryFromRaw> EventResult<T> {
/// Convert `EventResult<T>` into the equivalent `std::result::Result<T, InvalidEvent>`.
pub fn into_result(self) -> Result<T, InvalidEvent> {
match self {
EventResult::Ok(t) => Ok(t),
EventResult::Err(invalid_event) => Err(invalid_event),
}
}
}
/// Marks types that can be deserialized as EventResult<Self> (and don't need fallible conversion
/// from their raw type)
pub trait FromRaw: Sized {
/// The raw form of this event that deserialization falls back to if deserializing `Self` fails.
type Raw: DeserializeOwned;
fn from_raw(_: Self::Raw) -> Self;
}
pub trait TryFromRaw: Sized {
/// The raw form of this event that deserialization falls back to if deserializing `Self` fails.
type Raw: DeserializeOwned;
type Err: Display;
fn try_from_raw(_: Self::Raw) -> Result<Self, Self::Err>;
}
impl FromRaw for serde_json::Value {
type Raw = Self;
fn from_raw(raw: Self) -> Self {
raw
}
}
impl<K, V, S> FromRaw for HashMap<K, V, S>
where
Self: DeserializeOwned,
{
type Raw = Self;
fn from_raw(raw: Self) -> Self {
raw
}
}
impl<T: FromRaw> TryFromRaw for T {
type Raw = <T as FromRaw>::Raw;
type Err = Infallible;
fn try_from_raw(raw: Self::Raw) -> Result<Self, Self::Err> {
Ok(Self::from_raw(raw))
}
}
impl<'de, T> Deserialize<'de> for EventResult<T>
where
T: TryFromRaw,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let json = serde_json::Value::deserialize(deserializer)?;
let raw_data: T::Raw = match serde_json::from_value(json.clone()) {
Ok(raw) => raw,
Err(error) => {
return Ok(EventResult::Err(InvalidEvent {
json,
message: error.to_string(),
kind: InvalidEventKind::Deserialization,
}));
}
};
match T::try_from_raw(raw_data) {
Ok(value) => Ok(EventResult::Ok(value)),
Err(err) => Ok(EventResult::Err(InvalidEvent {
message: err.to_string(),
json,
kind: InvalidEventKind::Validation,
})),
}
}
}
/// A basic event.
pub trait Event: Debug + Serialize + TryFromRaw {
/// The type of this event's `content` field.
type Content: Debug + Serialize;
/// The event's content.
fn content(&self) -> &Self::Content;
/// The type of the event.
fn event_type(&self) -> EventType;
}
/// An event within the context of a room.
pub trait RoomEvent: Event {
/// The unique identifier for the event.
fn event_id(&self) -> &ruma_identifiers::EventId;
/// Timestamp (milliseconds since the UNIX epoch) on originating homeserver when this event was
/// sent.
fn origin_server_ts(&self) -> js_int::UInt;
/// The unique identifier for the room associated with this event.
///
/// This can be `None` if the event came from a context where there is
/// no ambiguity which room it belongs to, like a `/sync` response for example.
fn room_id(&self) -> Option<&ruma_identifiers::RoomId>;
/// The unique identifier for the user who sent this event.
fn sender(&self) -> &ruma_identifiers::UserId;
/// Additional key-value pairs not signed by the homeserver.
fn unsigned(&self) -> Option<&serde_json::Value>;
}
/// An event that describes persistent state about a room.
pub trait StateEvent: RoomEvent {
/// The previous content for this state key, if any.
fn prev_content(&self) -> Option<&Self::Content>;
/// A key that determines which piece of room state the event represents.
fn state_key(&self) -> &str;
}
/// An event that is malformed or otherwise invalid.
///
/// When attempting to deserialize an `EventResult`, an error in the input data may cause
/// deserialization to fail, or the JSON structure may be correct, but additional constraints
/// defined in the matrix specification are not upheld. This type provides an error message and a
/// `serde_json::Value` representation of the invalid event, as well as a flag for which type of
/// error was encountered.
#[derive(Clone, Debug)]
pub struct InvalidEvent {
message: String,
json: Value,
kind: InvalidEventKind,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum InvalidEventKind {
Deserialization,
Validation,
}
impl InvalidEvent {
/// A message describing why the event is invalid.
pub fn message(&self) -> String {
self.message.clone()
}
/// The `serde_json::Value` representation of the invalid event.
pub fn json(&self) -> &Value {
&self.json
}
/// Returns whether this is a deserialization error.
pub fn is_deserialization(&self) -> bool {
self.kind == InvalidEventKind::Deserialization
}
/// Returns whether this is a validation error.
pub fn is_validation(&self) -> bool {
self.kind == InvalidEventKind::Validation
}
}
impl Display for InvalidEvent {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "{}", self.message())
}
}
pub fn serde_json_eq_try_from_raw<T>(de: T, se: serde_json::Value)
where
T: Clone + Debug + PartialEq + Serialize + TryFromRaw,
{
assert_eq!(se, serde_json::to_value(de.clone()).unwrap());
assert_eq!(
de,
serde_json::from_value::<EventResult<_>>(se)
.unwrap()
.into_result()
.unwrap()
);
}
// See note about wrapping macro expansion in a module from `src/lib.rs`
mod common_case {
use super::*;