Merge remote-tracking branch 'upstream/main' into conduwuit-changes
This commit is contained in:
commit
7136799881
@ -45,6 +45,8 @@ Improvements:
|
|||||||
- Add `dir` `Request` field on the `get_relating_events_with_rel_types` and
|
- Add `dir` `Request` field on the `get_relating_events_with_rel_types` and
|
||||||
`get_relating_events_with_rel_type_and_event_type` endpoints
|
`get_relating_events_with_rel_type_and_event_type` endpoints
|
||||||
- Add unstable support for moderator server support discovery, according to MSC4121
|
- Add unstable support for moderator server support discovery, according to MSC4121
|
||||||
|
- Add unstable support for the room summary endpoint from MSC3266 behind the
|
||||||
|
`unstable-msc3266` feature.
|
||||||
|
|
||||||
# 0.17.4
|
# 0.17.4
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ unstable-msc2448 = []
|
|||||||
unstable-msc2654 = []
|
unstable-msc2654 = []
|
||||||
unstable-msc2965 = []
|
unstable-msc2965 = []
|
||||||
unstable-msc2967 = []
|
unstable-msc2967 = []
|
||||||
|
unstable-msc3266 = []
|
||||||
unstable-msc3488 = []
|
unstable-msc3488 = []
|
||||||
unstable-msc3575 = []
|
unstable-msc3575 = []
|
||||||
unstable-msc3814 = []
|
unstable-msc3814 = []
|
||||||
|
@ -4,6 +4,8 @@ pub mod aliases;
|
|||||||
pub mod create_room;
|
pub mod create_room;
|
||||||
pub mod get_event_by_timestamp;
|
pub mod get_event_by_timestamp;
|
||||||
pub mod get_room_event;
|
pub mod get_room_event;
|
||||||
|
#[cfg(feature = "unstable-msc3266")]
|
||||||
|
pub mod get_summary;
|
||||||
pub mod report_content;
|
pub mod report_content;
|
||||||
pub mod upgrade_room;
|
pub mod upgrade_room;
|
||||||
|
|
||||||
|
135
crates/ruma-client-api/src/room/get_summary.rs
Normal file
135
crates/ruma-client-api/src/room/get_summary.rs
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
//! `GET /_matrix/client/v1/summary/{roomIdOrAlias}`
|
||||||
|
//!
|
||||||
|
//! Experimental API enabled with MSC3266.
|
||||||
|
//!
|
||||||
|
//! Returns a short description of the state of a room.
|
||||||
|
|
||||||
|
pub mod msc3266 {
|
||||||
|
//! `MSC3266` ([MSC])
|
||||||
|
//!
|
||||||
|
//! [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/3266
|
||||||
|
|
||||||
|
use js_int::UInt;
|
||||||
|
use ruma_common::{
|
||||||
|
api::{request, response, Metadata},
|
||||||
|
metadata,
|
||||||
|
room::RoomType,
|
||||||
|
space::SpaceRoomJoinRule,
|
||||||
|
EventEncryptionAlgorithm, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedRoomOrAliasId,
|
||||||
|
OwnedServerName, RoomVersionId,
|
||||||
|
};
|
||||||
|
use ruma_events::room::member::MembershipState;
|
||||||
|
|
||||||
|
const METADATA: Metadata = metadata! {
|
||||||
|
method: GET,
|
||||||
|
rate_limited: false,
|
||||||
|
authentication: AccessTokenOptional,
|
||||||
|
history: {
|
||||||
|
unstable => "/_matrix/client/unstable/im.nheko.summary/rooms/:room_id_or_alias/summary",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Request type for the `get_summary` endpoint.
|
||||||
|
#[request(error = crate::Error)]
|
||||||
|
pub struct Request {
|
||||||
|
/// Alias or ID of the room to be summarized.
|
||||||
|
#[ruma_api(path)]
|
||||||
|
pub room_id_or_alias: OwnedRoomOrAliasId,
|
||||||
|
|
||||||
|
/// A list of servers the homeserver should attempt to use to peek at the room.
|
||||||
|
///
|
||||||
|
/// Defaults to an empty `Vec`.
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
#[ruma_api(query)]
|
||||||
|
pub via: Vec<OwnedServerName>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Response type for the `get_summary` endpoint.
|
||||||
|
#[response(error = crate::Error)]
|
||||||
|
pub struct Response {
|
||||||
|
/// ID of the room (useful if it's an alias).
|
||||||
|
pub room_id: OwnedRoomId,
|
||||||
|
|
||||||
|
/// The canonical alias for this room, if set.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub canonical_alias: Option<OwnedRoomAliasId>,
|
||||||
|
|
||||||
|
/// Avatar of the room.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub avatar_url: Option<OwnedMxcUri>,
|
||||||
|
|
||||||
|
/// Whether guests can join the room.
|
||||||
|
pub guest_can_join: bool,
|
||||||
|
|
||||||
|
/// Name of the room.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub name: Option<String>,
|
||||||
|
|
||||||
|
/// Member count of the room.
|
||||||
|
pub num_joined_members: UInt,
|
||||||
|
|
||||||
|
/// Topic of the room.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub topic: Option<String>,
|
||||||
|
|
||||||
|
/// Whether the room history can be read without joining.
|
||||||
|
pub world_readable: bool,
|
||||||
|
|
||||||
|
/// Join rule of the room.
|
||||||
|
pub join_rule: SpaceRoomJoinRule,
|
||||||
|
|
||||||
|
/// Type of the room, if any.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub room_type: Option<RoomType>,
|
||||||
|
|
||||||
|
/// Version of the room.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub room_version: Option<RoomVersionId>,
|
||||||
|
|
||||||
|
/// The current membership of this user in the room.
|
||||||
|
///
|
||||||
|
/// This field will not be present when called unauthenticated, but is required when called
|
||||||
|
/// authenticated. It should be `leave` if the server doesn't know about the room, since
|
||||||
|
/// for all other membership states the server would know about the room already.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub membership: Option<MembershipState>,
|
||||||
|
|
||||||
|
/// If the room is encrypted, the algorithm used for this room.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub encryption: Option<EventEncryptionAlgorithm>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Request {
|
||||||
|
/// Creates a new `Request` with the given room or alias ID and via server names.
|
||||||
|
pub fn new(room_id_or_alias: OwnedRoomOrAliasId, via: Vec<OwnedServerName>) -> Self {
|
||||||
|
Self { room_id_or_alias, via }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Response {
|
||||||
|
/// Creates a new [`Response`] with all the mandatory fields set.
|
||||||
|
pub fn new(
|
||||||
|
room_id: OwnedRoomId,
|
||||||
|
join_rule: SpaceRoomJoinRule,
|
||||||
|
guest_can_join: bool,
|
||||||
|
num_joined_members: UInt,
|
||||||
|
world_readable: bool,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
room_id,
|
||||||
|
canonical_alias: None,
|
||||||
|
avatar_url: None,
|
||||||
|
guest_can_join,
|
||||||
|
name: None,
|
||||||
|
num_joined_members,
|
||||||
|
topic: None,
|
||||||
|
world_readable,
|
||||||
|
join_rule,
|
||||||
|
room_type: None,
|
||||||
|
room_version: None,
|
||||||
|
membership: None,
|
||||||
|
encryption: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,7 @@ Improvements:
|
|||||||
- Point links to the Matrix 1.10 specification
|
- Point links to the Matrix 1.10 specification
|
||||||
- Implement `as_str()` and `AsRef<str>` for `push::PredefinedRuleId`
|
- Implement `as_str()` and `AsRef<str>` for `push::PredefinedRuleId`
|
||||||
- Implement `kind()` for `push::Predefined{*}RuleId`
|
- Implement `kind()` for `push::Predefined{*}RuleId`
|
||||||
|
- Implement `Clone` for `MatrixToUri` and `MatrixUri`
|
||||||
|
|
||||||
# 0.12.1
|
# 0.12.1
|
||||||
|
|
||||||
|
@ -217,6 +217,10 @@ pub use ruma_macros::request;
|
|||||||
///
|
///
|
||||||
/// The generated code expects a `METADATA` constant of type [`Metadata`] to be in scope.
|
/// The generated code expects a `METADATA` constant of type [`Metadata`] to be in scope.
|
||||||
///
|
///
|
||||||
|
/// The status code of `OutgoingResponse` can be optionally overridden by adding the `status`
|
||||||
|
/// attribute to `response`. The attribute value must be a status code constant from
|
||||||
|
/// `http::StatusCode`, e.g. `IM_A_TEAPOT`.
|
||||||
|
///
|
||||||
/// ## Attributes
|
/// ## Attributes
|
||||||
///
|
///
|
||||||
/// To declare which part of the request a field belongs to:
|
/// To declare which part of the request a field belongs to:
|
||||||
@ -260,7 +264,7 @@ pub use ruma_macros::request;
|
|||||||
/// # #[request]
|
/// # #[request]
|
||||||
/// # pub struct Request { }
|
/// # pub struct Request { }
|
||||||
///
|
///
|
||||||
/// #[response]
|
/// #[response(status = IM_A_TEAPOT)]
|
||||||
/// pub struct Response {
|
/// pub struct Response {
|
||||||
/// #[serde(skip_serializing_if = "Option::is_none")]
|
/// #[serde(skip_serializing_if = "Option::is_none")]
|
||||||
/// pub foo: Option<String>,
|
/// pub foo: Option<String>,
|
||||||
|
@ -259,7 +259,7 @@ impl From<(&RoomAliasId, &EventId)> for MatrixId {
|
|||||||
/// in a formatting macro or via `.to_string()`).
|
/// in a formatting macro or via `.to_string()`).
|
||||||
///
|
///
|
||||||
/// [`matrix.to` URI]: https://spec.matrix.org/latest/appendices/#matrixto-navigation
|
/// [`matrix.to` URI]: https://spec.matrix.org/latest/appendices/#matrixto-navigation
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct MatrixToUri {
|
pub struct MatrixToUri {
|
||||||
id: MatrixId,
|
id: MatrixId,
|
||||||
via: Vec<OwnedServerName>,
|
via: Vec<OwnedServerName>,
|
||||||
@ -443,7 +443,7 @@ impl From<Box<str>> for UriAction {
|
|||||||
/// in a formatting macro or via `.to_string()`).
|
/// in a formatting macro or via `.to_string()`).
|
||||||
///
|
///
|
||||||
/// [`matrix:` URI]: https://spec.matrix.org/latest/appendices/#matrix-uri-scheme
|
/// [`matrix:` URI]: https://spec.matrix.org/latest/appendices/#matrix-uri-scheme
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct MatrixUri {
|
pub struct MatrixUri {
|
||||||
id: MatrixId,
|
id: MatrixId,
|
||||||
via: Vec<OwnedServerName>,
|
via: Vec<OwnedServerName>,
|
||||||
|
33
crates/ruma-common/tests/api/default_status.rs
Normal file
33
crates/ruma-common/tests/api/default_status.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#![allow(clippy::exhaustive_structs)]
|
||||||
|
|
||||||
|
use http::StatusCode;
|
||||||
|
use ruma_common::{
|
||||||
|
api::{request, response, Metadata, OutgoingResponse as _},
|
||||||
|
metadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
const METADATA: Metadata = metadata! {
|
||||||
|
method: GET,
|
||||||
|
rate_limited: false,
|
||||||
|
authentication: None,
|
||||||
|
history: {
|
||||||
|
unstable => "/_matrix/my/endpoint",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Request type for the `default_status` endpoint.
|
||||||
|
#[request]
|
||||||
|
pub struct Request {}
|
||||||
|
|
||||||
|
/// Response type for the `default_status` endpoint.
|
||||||
|
#[response]
|
||||||
|
pub struct Response {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_default_status() {
|
||||||
|
let res = Response {};
|
||||||
|
let http_res = res.try_into_http_response::<Vec<u8>>().unwrap();
|
||||||
|
|
||||||
|
// Test that we correctly changed the status code.
|
||||||
|
assert_eq!(http_res.status(), StatusCode::OK);
|
||||||
|
}
|
@ -2,9 +2,11 @@
|
|||||||
#![allow(unreachable_pub)]
|
#![allow(unreachable_pub)]
|
||||||
|
|
||||||
mod conversions;
|
mod conversions;
|
||||||
|
mod default_status;
|
||||||
mod header_override;
|
mod header_override;
|
||||||
mod manual_endpoint_impl;
|
mod manual_endpoint_impl;
|
||||||
mod no_fields;
|
mod no_fields;
|
||||||
mod optional_headers;
|
mod optional_headers;
|
||||||
mod ruma_api;
|
mod ruma_api;
|
||||||
mod ruma_api_macros;
|
mod ruma_api_macros;
|
||||||
|
mod status_override;
|
||||||
|
50
crates/ruma-common/tests/api/status_override.rs
Normal file
50
crates/ruma-common/tests/api/status_override.rs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#![allow(clippy::exhaustive_structs)]
|
||||||
|
|
||||||
|
use http::{
|
||||||
|
header::{Entry, LOCATION},
|
||||||
|
StatusCode,
|
||||||
|
};
|
||||||
|
use ruma_common::{
|
||||||
|
api::{request, response, Metadata, OutgoingResponse as _},
|
||||||
|
metadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
const METADATA: Metadata = metadata! {
|
||||||
|
method: GET,
|
||||||
|
rate_limited: false,
|
||||||
|
authentication: None,
|
||||||
|
history: {
|
||||||
|
unstable => "/_matrix/my/endpoint",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Request type for the `status_override` endpoint.
|
||||||
|
#[request]
|
||||||
|
pub struct Request {}
|
||||||
|
|
||||||
|
/// Response type for the `status_override` endpoint.
|
||||||
|
#[response(status = FOUND)]
|
||||||
|
pub struct Response {
|
||||||
|
#[ruma_api(header = LOCATION)]
|
||||||
|
pub location: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_status_override() {
|
||||||
|
let res = Response { location: Some("/_matrix/another/endpoint".into()) };
|
||||||
|
let mut http_res = res.try_into_http_response::<Vec<u8>>().unwrap();
|
||||||
|
|
||||||
|
// Test that we correctly changed the status code.
|
||||||
|
assert_eq!(http_res.status(), StatusCode::FOUND);
|
||||||
|
|
||||||
|
// Test that we correctly replaced the location,
|
||||||
|
// not adding another location header.
|
||||||
|
assert_eq!(
|
||||||
|
match http_res.headers_mut().entry(LOCATION) {
|
||||||
|
Entry::Occupied(occ) => occ.iter().count(),
|
||||||
|
_ => 0,
|
||||||
|
},
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert_eq!(http_res.headers().get("location").unwrap(), "/_matrix/another/endpoint");
|
||||||
|
}
|
@ -1,8 +1,15 @@
|
|||||||
# [unreleased]
|
# [unreleased]
|
||||||
|
|
||||||
|
Breaking Changes:
|
||||||
|
|
||||||
|
- Do not export `Node` in the public API, it is not usable on its own and it is
|
||||||
|
not in the output of any public method.
|
||||||
|
|
||||||
Improvements:
|
Improvements:
|
||||||
|
|
||||||
- Add support for deprecated HTML tags, according to Matrix 1.10
|
- Add support for deprecated HTML tags, according to Matrix 1.10
|
||||||
|
- Allow to navigate through the HTML tree with `Html::first_child()`,
|
||||||
|
`Html::last_child()` or `Html::children()`
|
||||||
|
|
||||||
# 0.1.0
|
# 0.1.0
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{collections::BTreeSet, fmt, io};
|
use std::{collections::BTreeSet, fmt, io, iter::FusedIterator};
|
||||||
|
|
||||||
use as_variant::as_variant;
|
use as_variant::as_variant;
|
||||||
use html5ever::{
|
use html5ever::{
|
||||||
@ -112,6 +112,40 @@ impl Html {
|
|||||||
self.nodes[parent].first_child = next_sibling;
|
self.nodes[parent].first_child = next_sibling;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the ID of the root node of the HTML.
|
||||||
|
pub(crate) fn root_id(&self) -> usize {
|
||||||
|
self.nodes[0].first_child.expect("html should always have a root node")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the root node of the HTML.
|
||||||
|
pub(crate) fn root(&self) -> &Node {
|
||||||
|
&self.nodes[self.root_id()]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the root node of the HTML has children.
|
||||||
|
pub fn has_children(&self) -> bool {
|
||||||
|
self.root().first_child.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The first child node of the root node of the HTML.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the root node has no children.
|
||||||
|
pub fn first_child(&self) -> Option<NodeRef<'_>> {
|
||||||
|
self.root().first_child.map(|id| NodeRef::new(self, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The last child node of the root node of the HTML .
|
||||||
|
///
|
||||||
|
/// Returns `None` if the root node has no children.
|
||||||
|
pub fn last_child(&self) -> Option<NodeRef<'_>> {
|
||||||
|
self.root().last_child.map(|id| NodeRef::new(self, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate through the children of the root node of the HTML.
|
||||||
|
pub fn children(&self) -> Children<'_> {
|
||||||
|
Children::new(self.first_child())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Html {
|
impl Default for Html {
|
||||||
@ -252,9 +286,9 @@ impl Serialize for Html {
|
|||||||
{
|
{
|
||||||
match traversal_scope {
|
match traversal_scope {
|
||||||
TraversalScope::IncludeNode => {
|
TraversalScope::IncludeNode => {
|
||||||
let root = self.nodes[0].first_child.unwrap();
|
let root = self.root();
|
||||||
|
|
||||||
let mut next_child = self.nodes[root].first_child;
|
let mut next_child = root.first_child;
|
||||||
while let Some(child) = next_child {
|
while let Some(child) = next_child {
|
||||||
let child = &self.nodes[child];
|
let child = &self.nodes[child];
|
||||||
child.serialize(self, serializer)?;
|
child.serialize(self, serializer)?;
|
||||||
@ -287,7 +321,7 @@ impl fmt::Display for Html {
|
|||||||
/// An HTML node.
|
/// An HTML node.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct Node {
|
pub(crate) struct Node {
|
||||||
pub(crate) parent: Option<usize>,
|
pub(crate) parent: Option<usize>,
|
||||||
pub(crate) prev_sibling: Option<usize>,
|
pub(crate) prev_sibling: Option<usize>,
|
||||||
pub(crate) next_sibling: Option<usize>,
|
pub(crate) next_sibling: Option<usize>,
|
||||||
@ -310,7 +344,7 @@ impl Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the data of this `Node` if it is an Element (aka an HTML tag).
|
/// Returns the data of this `Node` if it is an Element (aka an HTML tag).
|
||||||
pub fn as_element(&self) -> Option<&ElementData> {
|
pub(crate) fn as_element(&self) -> Option<&ElementData> {
|
||||||
as_variant!(&self.data, NodeData::Element)
|
as_variant!(&self.data, NodeData::Element)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,6 +353,11 @@ impl Node {
|
|||||||
as_variant!(&mut self.data, NodeData::Element)
|
as_variant!(&mut self.data, NodeData::Element)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the text content of this `Node`, if it is a `NodeData::Text`.
|
||||||
|
fn as_text(&self) -> Option<&StrTendril> {
|
||||||
|
as_variant!(&self.data, NodeData::Text)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the mutable text content of this `Node`, if it is a `NodeData::Text`.
|
/// Returns the mutable text content of this `Node`, if it is a `NodeData::Text`.
|
||||||
fn as_text_mut(&mut self) -> Option<&mut StrTendril> {
|
fn as_text_mut(&mut self) -> Option<&mut StrTendril> {
|
||||||
as_variant!(&mut self.data, NodeData::Text)
|
as_variant!(&mut self.data, NodeData::Text)
|
||||||
@ -365,9 +404,9 @@ impl Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The data of a `Node`.
|
/// The data of a `Node`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
#[allow(clippy::exhaustive_enums)]
|
#[allow(clippy::exhaustive_enums)]
|
||||||
pub(crate) enum NodeData {
|
pub enum NodeData {
|
||||||
/// The root node of the `Html`.
|
/// The root node of the `Html`.
|
||||||
Document,
|
Document,
|
||||||
|
|
||||||
@ -382,7 +421,7 @@ pub(crate) enum NodeData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The data of an HTML element.
|
/// The data of an HTML element.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
#[allow(clippy::exhaustive_structs)]
|
#[allow(clippy::exhaustive_structs)]
|
||||||
pub struct ElementData {
|
pub struct ElementData {
|
||||||
/// The qualified name of the element.
|
/// The qualified name of the element.
|
||||||
@ -392,6 +431,123 @@ pub struct ElementData {
|
|||||||
pub attrs: BTreeSet<Attribute>,
|
pub attrs: BTreeSet<Attribute>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A reference to an HTML node.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct NodeRef<'a> {
|
||||||
|
/// The `Html` struct containing the nodes.
|
||||||
|
pub(crate) html: &'a Html,
|
||||||
|
/// The referenced node.
|
||||||
|
pub(crate) node: &'a Node,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> NodeRef<'a> {
|
||||||
|
/// Construct a new `NodeRef` for the given HTML and node ID.
|
||||||
|
fn new(html: &'a Html, id: usize) -> Self {
|
||||||
|
Self { html, node: &html.nodes[id] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a new `NodeRef` from the same HTML as this node with the given node ID.
|
||||||
|
fn with_id(&self, id: usize) -> Self {
|
||||||
|
let html = self.html;
|
||||||
|
Self::new(html, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The data of the node.
|
||||||
|
pub fn data(&self) -> &'a NodeData {
|
||||||
|
&self.node.data
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the data of this node if it is a `NodeData::Element`.
|
||||||
|
pub fn as_element(&self) -> Option<&'a ElementData> {
|
||||||
|
self.node.as_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the text content of this node, if it is a `NodeData::Text`.
|
||||||
|
pub fn as_text(&self) -> Option<&'a StrTendril> {
|
||||||
|
self.node.as_text()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The parent node of this node.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the parent is the root node.
|
||||||
|
pub fn parent(&self) -> Option<NodeRef<'a>> {
|
||||||
|
let parent_id = self.node.parent?;
|
||||||
|
|
||||||
|
// We don't want users to be able to navigate to the root.
|
||||||
|
if parent_id == self.html.root_id() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(self.with_id(parent_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The next sibling node of this node.
|
||||||
|
///
|
||||||
|
/// Returns `None` if this is the last of its siblings.
|
||||||
|
pub fn next_sibling(&self) -> Option<NodeRef<'a>> {
|
||||||
|
Some(self.with_id(self.node.next_sibling?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The previous sibling node of this node.
|
||||||
|
///
|
||||||
|
/// Returns `None` if this is the first of its siblings.
|
||||||
|
pub fn prev_sibling(&self) -> Option<NodeRef<'a>> {
|
||||||
|
Some(self.with_id(self.node.prev_sibling?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this node has children.
|
||||||
|
pub fn has_children(&self) -> bool {
|
||||||
|
self.node.first_child.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The first child node of this node.
|
||||||
|
///
|
||||||
|
/// Returns `None` if this node has no children.
|
||||||
|
pub fn first_child(&self) -> Option<NodeRef<'a>> {
|
||||||
|
Some(self.with_id(self.node.first_child?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The last child node of this node.
|
||||||
|
///
|
||||||
|
/// Returns `None` if this node has no children.
|
||||||
|
pub fn last_child(&self) -> Option<NodeRef<'a>> {
|
||||||
|
Some(self.with_id(self.node.last_child?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an iterator through the children of this node.
|
||||||
|
pub fn children(&self) -> Children<'a> {
|
||||||
|
Children::new(self.first_child())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator through the children of a node.
|
||||||
|
///
|
||||||
|
/// Can be constructed with [`Html::children()`] or [`NodeRef::children()`].
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Children<'a> {
|
||||||
|
next: Option<NodeRef<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Children<'a> {
|
||||||
|
/// Construct a `Children` starting from the given node.
|
||||||
|
fn new(start_node: Option<NodeRef<'a>>) -> Self {
|
||||||
|
Self { next: start_node }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for Children<'a> {
|
||||||
|
type Item = NodeRef<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let next = self.next?;
|
||||||
|
self.next = next.next_sibling();
|
||||||
|
Some(next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FusedIterator for Children<'a> {}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Html;
|
use super::Html;
|
||||||
|
@ -14,8 +14,4 @@ mod helpers;
|
|||||||
mod html;
|
mod html;
|
||||||
mod sanitizer_config;
|
mod sanitizer_config;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{helpers::*, html::*, sanitizer_config::SanitizerConfig};
|
||||||
helpers::*,
|
|
||||||
html::{ElementData, Html, Node},
|
|
||||||
sanitizer_config::SanitizerConfig,
|
|
||||||
};
|
|
||||||
|
@ -96,8 +96,8 @@ impl SanitizerConfig {
|
|||||||
|
|
||||||
/// Clean the given HTML with this sanitizer.
|
/// Clean the given HTML with this sanitizer.
|
||||||
pub(crate) fn clean(self, html: &mut Html) {
|
pub(crate) fn clean(self, html: &mut Html) {
|
||||||
let root = html.nodes[0].first_child.unwrap();
|
let root = html.root();
|
||||||
let mut next_child = html.nodes[root].first_child;
|
let mut next_child = root.first_child;
|
||||||
|
|
||||||
while let Some(child) = next_child {
|
while let Some(child) = next_child {
|
||||||
next_child = html.nodes[child].next_sibling;
|
next_child = html.nodes[child].next_sibling;
|
||||||
|
@ -1 +1,2 @@
|
|||||||
|
mod navigate;
|
||||||
mod sanitize;
|
mod sanitize;
|
||||||
|
140
crates/ruma-html/tests/it/html/navigate.rs
Normal file
140
crates/ruma-html/tests/it/html/navigate.rs
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
use ruma_html::Html;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn navigate_tree() {
|
||||||
|
let raw_html = "\
|
||||||
|
<h1>Title</h1>\
|
||||||
|
<div class=\"text\">\
|
||||||
|
<p>This is some <em>text</em></p>\
|
||||||
|
</div>\
|
||||||
|
";
|
||||||
|
let html = Html::parse(raw_html);
|
||||||
|
|
||||||
|
assert!(html.has_children());
|
||||||
|
assert!(html.first_child().is_some());
|
||||||
|
assert!(html.last_child().is_some());
|
||||||
|
|
||||||
|
let mut html_children = html.children();
|
||||||
|
|
||||||
|
// `<h1>` element.
|
||||||
|
let h1_node = html_children.next().unwrap();
|
||||||
|
|
||||||
|
let h1_element = h1_node.as_element().unwrap();
|
||||||
|
assert_eq!(&h1_element.name.local, "h1");
|
||||||
|
assert!(h1_element.attrs.is_empty());
|
||||||
|
|
||||||
|
assert!(h1_node.parent().is_none());
|
||||||
|
assert!(h1_node.next_sibling().is_some());
|
||||||
|
assert!(h1_node.prev_sibling().is_none());
|
||||||
|
assert!(h1_node.has_children());
|
||||||
|
assert!(h1_node.first_child().is_some());
|
||||||
|
assert!(h1_node.last_child().is_some());
|
||||||
|
|
||||||
|
let mut h1_children = h1_node.children();
|
||||||
|
|
||||||
|
// Text of `<h1>` element.
|
||||||
|
let h1_text_node = h1_children.next().unwrap();
|
||||||
|
let h1_text = h1_text_node.as_text().unwrap();
|
||||||
|
assert_eq!(h1_text.as_ref(), "Title");
|
||||||
|
|
||||||
|
assert!(h1_text_node.parent().is_some());
|
||||||
|
assert!(h1_text_node.next_sibling().is_none());
|
||||||
|
assert!(h1_text_node.prev_sibling().is_none());
|
||||||
|
assert!(!h1_text_node.has_children());
|
||||||
|
assert!(h1_text_node.first_child().is_none());
|
||||||
|
assert!(h1_text_node.last_child().is_none());
|
||||||
|
|
||||||
|
let mut h1_text_children = h1_text_node.children();
|
||||||
|
assert!(h1_text_children.next().is_none());
|
||||||
|
|
||||||
|
assert!(h1_children.next().is_none());
|
||||||
|
|
||||||
|
// `<div>` element.
|
||||||
|
let div_node = html_children.next().unwrap();
|
||||||
|
|
||||||
|
let div_element = div_node.as_element().unwrap();
|
||||||
|
assert_eq!(&div_element.name.local, "div");
|
||||||
|
assert_eq!(div_element.attrs.len(), 1);
|
||||||
|
let class_attr = div_element.attrs.first().unwrap();
|
||||||
|
assert_eq!(&class_attr.name.local, "class");
|
||||||
|
assert_eq!(class_attr.value.as_ref(), "text");
|
||||||
|
|
||||||
|
assert!(div_node.parent().is_none());
|
||||||
|
assert!(div_node.next_sibling().is_none());
|
||||||
|
assert!(div_node.prev_sibling().is_some());
|
||||||
|
assert!(div_node.has_children());
|
||||||
|
assert!(div_node.first_child().is_some());
|
||||||
|
assert!(div_node.last_child().is_some());
|
||||||
|
|
||||||
|
let mut div_children = div_node.children();
|
||||||
|
|
||||||
|
// `<p>` element.
|
||||||
|
let p_node = div_children.next().unwrap();
|
||||||
|
|
||||||
|
let p_element = p_node.as_element().unwrap();
|
||||||
|
assert_eq!(&p_element.name.local, "p");
|
||||||
|
assert!(p_element.attrs.is_empty());
|
||||||
|
|
||||||
|
assert!(p_node.parent().is_some());
|
||||||
|
assert!(p_node.next_sibling().is_none());
|
||||||
|
assert!(p_node.prev_sibling().is_none());
|
||||||
|
assert!(p_node.has_children());
|
||||||
|
assert!(p_node.first_child().is_some());
|
||||||
|
assert!(p_node.last_child().is_some());
|
||||||
|
|
||||||
|
let mut p_children = p_node.children();
|
||||||
|
|
||||||
|
// Text of `<p>` element.
|
||||||
|
let p_text_node = p_children.next().unwrap();
|
||||||
|
let p_text = p_text_node.as_text().unwrap();
|
||||||
|
assert_eq!(p_text.as_ref(), "This is some ");
|
||||||
|
|
||||||
|
assert!(p_text_node.parent().is_some());
|
||||||
|
assert!(p_text_node.next_sibling().is_some());
|
||||||
|
assert!(p_text_node.prev_sibling().is_none());
|
||||||
|
assert!(!p_text_node.has_children());
|
||||||
|
assert!(p_text_node.first_child().is_none());
|
||||||
|
assert!(p_text_node.last_child().is_none());
|
||||||
|
|
||||||
|
let mut p_text_children = p_text_node.children();
|
||||||
|
assert!(p_text_children.next().is_none());
|
||||||
|
|
||||||
|
// `<em>` element.
|
||||||
|
let em_node = p_children.next().unwrap();
|
||||||
|
|
||||||
|
let em_element = em_node.as_element().unwrap();
|
||||||
|
assert_eq!(&em_element.name.local, "em");
|
||||||
|
assert!(em_element.attrs.is_empty());
|
||||||
|
|
||||||
|
assert!(em_node.parent().is_some());
|
||||||
|
assert!(em_node.next_sibling().is_none());
|
||||||
|
assert!(em_node.prev_sibling().is_some());
|
||||||
|
assert!(em_node.has_children());
|
||||||
|
assert!(em_node.first_child().is_some());
|
||||||
|
assert!(em_node.last_child().is_some());
|
||||||
|
|
||||||
|
let mut em_children = em_node.children();
|
||||||
|
|
||||||
|
// Text of `<em>` element.
|
||||||
|
let em_text_node = em_children.next().unwrap();
|
||||||
|
let em_text = em_text_node.as_text().unwrap();
|
||||||
|
assert_eq!(em_text.as_ref(), "text");
|
||||||
|
|
||||||
|
assert!(em_text_node.parent().is_some());
|
||||||
|
assert!(em_text_node.next_sibling().is_none());
|
||||||
|
assert!(em_text_node.prev_sibling().is_none());
|
||||||
|
assert!(!em_text_node.has_children());
|
||||||
|
assert!(em_text_node.first_child().is_none());
|
||||||
|
assert!(em_text_node.last_child().is_none());
|
||||||
|
|
||||||
|
let mut em_text_children = em_text_node.children();
|
||||||
|
assert!(em_text_children.next().is_none());
|
||||||
|
|
||||||
|
assert!(em_children.next().is_none());
|
||||||
|
|
||||||
|
assert!(p_children.next().is_none());
|
||||||
|
|
||||||
|
assert!(div_children.next().is_none());
|
||||||
|
|
||||||
|
assert!(html_children.next().is_none());
|
||||||
|
}
|
@ -14,6 +14,7 @@ mod kw {
|
|||||||
syn::custom_keyword!(header);
|
syn::custom_keyword!(header);
|
||||||
syn::custom_keyword!(error);
|
syn::custom_keyword!(error);
|
||||||
syn::custom_keyword!(manual_body_serde);
|
syn::custom_keyword!(manual_body_serde);
|
||||||
|
syn::custom_keyword!(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum RequestMeta {
|
pub enum RequestMeta {
|
||||||
@ -99,6 +100,7 @@ impl Parse for ResponseMeta {
|
|||||||
pub enum DeriveResponseMeta {
|
pub enum DeriveResponseMeta {
|
||||||
ManualBodySerde,
|
ManualBodySerde,
|
||||||
Error(Type),
|
Error(Type),
|
||||||
|
Status(Ident),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for DeriveResponseMeta {
|
impl Parse for DeriveResponseMeta {
|
||||||
@ -111,6 +113,10 @@ impl Parse for DeriveResponseMeta {
|
|||||||
let _: kw::error = input.parse()?;
|
let _: kw::error = input.parse()?;
|
||||||
let _: Token![=] = input.parse()?;
|
let _: Token![=] = input.parse()?;
|
||||||
input.parse().map(Self::Error)
|
input.parse().map(Self::Error)
|
||||||
|
} else if lookahead.peek(kw::status) {
|
||||||
|
let _: kw::status = input.parse()?;
|
||||||
|
let _: Token![=] = input.parse()?;
|
||||||
|
input.parse().map(Self::Status)
|
||||||
} else {
|
} else {
|
||||||
Err(lookahead.error())
|
Err(lookahead.error())
|
||||||
}
|
}
|
||||||
|
@ -32,13 +32,21 @@ pub fn expand_response(attr: ResponseAttr, item: ItemStruct) -> TokenStream {
|
|||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| quote! { #ruma_common::api::error::MatrixError });
|
.unwrap_or_else(|| quote! { #ruma_common::api::error::MatrixError });
|
||||||
|
let status_ident = attr
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.find_map(|a| match a {
|
||||||
|
DeriveResponseMeta::Status(ident) => Some(quote! { #ident }),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| quote! { OK });
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#maybe_feature_error
|
#maybe_feature_error
|
||||||
|
|
||||||
#[derive(Clone, Debug, #ruma_macros::Response, #ruma_common::serde::_FakeDeriveSerde)]
|
#[derive(Clone, Debug, #ruma_macros::Response, #ruma_common::serde::_FakeDeriveSerde)]
|
||||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
#[ruma_api(error = #error_ty)]
|
#[ruma_api(error = #error_ty, status = #status_ident)]
|
||||||
#item
|
#item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,6 +68,7 @@ pub fn expand_derive_response(input: DeriveInput) -> syn::Result<TokenStream> {
|
|||||||
let fields = fields.into_iter().map(ResponseField::try_from).collect::<syn::Result<_>>()?;
|
let fields = fields.into_iter().map(ResponseField::try_from).collect::<syn::Result<_>>()?;
|
||||||
let mut manual_body_serde = false;
|
let mut manual_body_serde = false;
|
||||||
let mut error_ty = None;
|
let mut error_ty = None;
|
||||||
|
let mut status_ident = None;
|
||||||
for attr in input.attrs {
|
for attr in input.attrs {
|
||||||
if !attr.path().is_ident("ruma_api") {
|
if !attr.path().is_ident("ruma_api") {
|
||||||
continue;
|
continue;
|
||||||
@ -71,6 +80,7 @@ pub fn expand_derive_response(input: DeriveInput) -> syn::Result<TokenStream> {
|
|||||||
match meta {
|
match meta {
|
||||||
DeriveResponseMeta::ManualBodySerde => manual_body_serde = true,
|
DeriveResponseMeta::ManualBodySerde => manual_body_serde = true,
|
||||||
DeriveResponseMeta::Error(t) => error_ty = Some(t),
|
DeriveResponseMeta::Error(t) => error_ty = Some(t),
|
||||||
|
DeriveResponseMeta::Status(t) => status_ident = Some(t),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,6 +91,7 @@ pub fn expand_derive_response(input: DeriveInput) -> syn::Result<TokenStream> {
|
|||||||
fields,
|
fields,
|
||||||
manual_body_serde,
|
manual_body_serde,
|
||||||
error_ty: error_ty.unwrap(),
|
error_ty: error_ty.unwrap(),
|
||||||
|
status_ident: status_ident.unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
response.check()?;
|
response.check()?;
|
||||||
@ -93,6 +104,7 @@ struct Response {
|
|||||||
fields: Vec<ResponseField>,
|
fields: Vec<ResponseField>,
|
||||||
manual_body_serde: bool,
|
manual_body_serde: bool,
|
||||||
error_ty: Type,
|
error_ty: Type,
|
||||||
|
status_ident: Ident,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
@ -145,7 +157,7 @@ impl Response {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let outgoing_response_impl = self.expand_outgoing(&ruma_common);
|
let outgoing_response_impl = self.expand_outgoing(&self.status_ident, &ruma_common);
|
||||||
let incoming_response_impl = self.expand_incoming(&self.error_ty, &ruma_common);
|
let incoming_response_impl = self.expand_incoming(&self.error_ty, &ruma_common);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
use syn::Ident;
|
||||||
|
|
||||||
use super::{Response, ResponseField};
|
use super::{Response, ResponseField};
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
pub fn expand_outgoing(&self, ruma_common: &TokenStream) -> TokenStream {
|
pub fn expand_outgoing(&self, status_ident: &Ident, ruma_common: &TokenStream) -> TokenStream {
|
||||||
let bytes = quote! { #ruma_common::exports::bytes };
|
let bytes = quote! { #ruma_common::exports::bytes };
|
||||||
let http = quote! { #ruma_common::exports::http };
|
let http = quote! { #ruma_common::exports::http };
|
||||||
|
|
||||||
@ -68,6 +69,7 @@ impl Response {
|
|||||||
self,
|
self,
|
||||||
) -> ::std::result::Result<#http::Response<T>, #ruma_common::api::error::IntoHttpError> {
|
) -> ::std::result::Result<#http::Response<T>, #ruma_common::api::error::IntoHttpError> {
|
||||||
let mut resp_builder = #http::Response::builder()
|
let mut resp_builder = #http::Response::builder()
|
||||||
|
.status(#http::StatusCode::#status_ident)
|
||||||
.header(#http::header::CONTENT_TYPE, "application/json");
|
.header(#http::header::CONTENT_TYPE, "application/json");
|
||||||
|
|
||||||
if let Some(mut headers) = resp_builder.headers_mut() {
|
if let Some(mut headers) = resp_builder.headers_mut() {
|
||||||
|
@ -246,6 +246,7 @@ unstable-msc3245 = ["ruma-events?/unstable-msc3245"]
|
|||||||
# https://github.com/matrix-org/matrix-spec-proposals/blob/83f6c5b469c1d78f714e335dcaa25354b255ffa5/proposals/3245-voice-messages.md
|
# https://github.com/matrix-org/matrix-spec-proposals/blob/83f6c5b469c1d78f714e335dcaa25354b255ffa5/proposals/3245-voice-messages.md
|
||||||
unstable-msc3245-v1-compat = ["ruma-events?/unstable-msc3245-v1-compat"]
|
unstable-msc3245-v1-compat = ["ruma-events?/unstable-msc3245-v1-compat"]
|
||||||
unstable-msc3246 = ["ruma-events?/unstable-msc3246"]
|
unstable-msc3246 = ["ruma-events?/unstable-msc3246"]
|
||||||
|
unstable-msc3266 = ["ruma-client-api?/unstable-msc3266"]
|
||||||
unstable-msc3381 = ["ruma-events?/unstable-msc3381"]
|
unstable-msc3381 = ["ruma-events?/unstable-msc3381"]
|
||||||
unstable-msc3401 = ["ruma-events?/unstable-msc3401"]
|
unstable-msc3401 = ["ruma-events?/unstable-msc3401"]
|
||||||
unstable-msc3488 = [
|
unstable-msc3488 = [
|
||||||
@ -299,6 +300,7 @@ __ci = [
|
|||||||
"unstable-msc3245",
|
"unstable-msc3245",
|
||||||
"unstable-msc3245-v1-compat",
|
"unstable-msc3245-v1-compat",
|
||||||
"unstable-msc3246",
|
"unstable-msc3246",
|
||||||
|
"unstable-msc3266",
|
||||||
"unstable-msc3381",
|
"unstable-msc3381",
|
||||||
"unstable-msc3401",
|
"unstable-msc3401",
|
||||||
"unstable-msc3488",
|
"unstable-msc3488",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user