Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{AnyId, JmapObjectId},
|
||||
references::{
|
||||
Graph,
|
||||
jsptr::{EvalResults, ResponsePtr},
|
||||
},
|
||||
request::reference::ResultReference,
|
||||
response::{ChangesResponseMethod, GetResponseMethod, Response, ResponseMethod},
|
||||
};
|
||||
use compact_str::format_compact;
|
||||
use jmap_tools::{Element, Key, Property, Value};
|
||||
use types::{blob::BlobId, id::Id};
|
||||
|
||||
impl Response<'_> {
|
||||
pub(crate) fn eval_result_references(&self, rr: &ResultReference) -> trc::Result<EvalResults> {
|
||||
let mut results = EvalResults::default();
|
||||
|
||||
for response in &self.method_responses {
|
||||
if response.id == rr.result_of && response.name == rr.name {
|
||||
let path = rr.path.iter();
|
||||
let success = match &response.method {
|
||||
ResponseMethod::Get(response) => match response {
|
||||
GetResponseMethod::Email(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::Mailbox(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::Thread(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::Identity(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::EmailSubmission(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::PushSubscription(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::Sieve(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::VacationResponse(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::Principal(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::Quota(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::Blob(response) => response.eval_jptr(path, &mut results),
|
||||
GetResponseMethod::AddressBook(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::ContactCard(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::FileNode(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::Calendar(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::CalendarEvent(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::CalendarEventNotification(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::ParticipantIdentity(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::ShareNotification(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::PrincipalAvailability(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::Registry(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
},
|
||||
ResponseMethod::Changes(response) => match response {
|
||||
ChangesResponseMethod::Email(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
ChangesResponseMethod::Mailbox(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
ChangesResponseMethod::Thread(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
ChangesResponseMethod::Identity(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
ChangesResponseMethod::EmailSubmission(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
ChangesResponseMethod::Quota(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
ChangesResponseMethod::AddressBook(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
ChangesResponseMethod::ContactCard(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
ChangesResponseMethod::FileNode(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
ChangesResponseMethod::Calendar(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
ChangesResponseMethod::CalendarEvent(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
ChangesResponseMethod::CalendarEventNotification(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
ChangesResponseMethod::ShareNotification(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
},
|
||||
ResponseMethod::Query(response) => response.eval_jptr(path, &mut results),
|
||||
ResponseMethod::QueryChanges(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if success {
|
||||
return Ok(results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(trc::JmapEvent::InvalidResultReference
|
||||
.into_err()
|
||||
.details(format_compact!(
|
||||
"Result reference to {}#{} not found.",
|
||||
rr.result_of,
|
||||
rr.name
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn eval_id_reference(&self, ir: &str) -> trc::Result<Id> {
|
||||
if let Some(AnyId::Id(id)) = self.created_ids.get(ir) {
|
||||
Ok(*id)
|
||||
} else {
|
||||
Err(trc::JmapEvent::InvalidResultReference
|
||||
.into_err()
|
||||
.details(format_compact!("Id reference {ir:?} not found.")))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn eval_blob_id_reference(&self, ir: &str) -> trc::Result<BlobId> {
|
||||
if let Some(AnyId::BlobId(id)) = self.created_ids.get(ir) {
|
||||
Ok(id.clone())
|
||||
} else {
|
||||
Err(trc::JmapEvent::InvalidResultReference
|
||||
.into_err()
|
||||
.details(format_compact!("blobId reference {ir:?} not found.")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait EvalObjectReferences {
|
||||
fn eval_object_references(
|
||||
&mut self,
|
||||
response: &Response<'_>,
|
||||
graph: &mut Graph<'_>,
|
||||
depth: usize,
|
||||
max_depth: usize,
|
||||
eval_strings: bool,
|
||||
) -> trc::Result<()>;
|
||||
}
|
||||
|
||||
impl<'x, P, E> EvalObjectReferences for Value<'x, P, E>
|
||||
where
|
||||
P: Property + JmapObjectId,
|
||||
E: Element<Property = P> + JmapObjectId,
|
||||
{
|
||||
fn eval_object_references(
|
||||
&mut self,
|
||||
response: &Response<'_>,
|
||||
graph: &mut Graph<'_>,
|
||||
depth: usize,
|
||||
max_depth: usize,
|
||||
eval_strings: bool,
|
||||
) -> trc::Result<()> {
|
||||
match self {
|
||||
Value::Element(element) => {
|
||||
if let Some(id_ref) = element.as_id_ref() {
|
||||
if let Some(id) = response.created_ids.get(id_ref) {
|
||||
if !element.try_set_id(id.clone()) {
|
||||
return Err(trc::JmapEvent::InvalidResultReference
|
||||
.into_err()
|
||||
.details("Id reference points to invalid type."));
|
||||
}
|
||||
} else if let Graph::Some { child_id, graph } = graph {
|
||||
graph
|
||||
.entry(child_id.to_string())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(id_ref.to_string());
|
||||
} else {
|
||||
return Err(trc::JmapEvent::InvalidResultReference
|
||||
.into_err()
|
||||
.details(format_compact!("Id reference {id_ref:?} not found.")));
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Array(items) if depth < max_depth => {
|
||||
// Resolve references in arrays (e.g. emailIds: [#idRef1, #idRef2])
|
||||
for item in items {
|
||||
item.eval_object_references(
|
||||
response,
|
||||
graph,
|
||||
depth + 1,
|
||||
max_depth,
|
||||
eval_strings,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Value::Object(items) if depth < max_depth => {
|
||||
// Resolve references in JMAP sets (e.g. mailboxIds: { "#idRef1": true, "#idRef2": true })
|
||||
for (key, value) in items.as_mut_vec() {
|
||||
if let Key::Property(property) = key
|
||||
&& let Some(id_ref) = property.as_id_ref()
|
||||
{
|
||||
if let Some(id) = response.created_ids.get(id_ref) {
|
||||
if !property.try_set_id(id.clone()) {
|
||||
return Err(trc::JmapEvent::InvalidResultReference
|
||||
.into_err()
|
||||
.details("Id reference points to invalid type."));
|
||||
}
|
||||
} else {
|
||||
return Err(trc::JmapEvent::InvalidResultReference
|
||||
.into_err()
|
||||
.details(format_compact!("Id reference {id_ref:?} not found.")));
|
||||
}
|
||||
} else if eval_strings
|
||||
&& let Some(id) = key
|
||||
.as_string_key()
|
||||
.and_then(|k| k.strip_prefix('#'))
|
||||
.and_then(|id_ref| response.created_ids.get(id_ref))
|
||||
{
|
||||
*key = Key::Owned(match id {
|
||||
AnyId::Id(id) => id.to_string(),
|
||||
AnyId::BlobId(id) => id.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
if matches!(
|
||||
value,
|
||||
Value::Element(_) | Value::Array(_) | Value::Object(_)
|
||||
) {
|
||||
value.eval_object_references(
|
||||
response,
|
||||
graph,
|
||||
depth + 1,
|
||||
max_depth,
|
||||
eval_strings,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
method::{
|
||||
PropertyWrapper,
|
||||
availability::{BusyPeriod, GetAvailabilityResponse},
|
||||
changes::ChangesResponse,
|
||||
get::GetResponse,
|
||||
query::QueryResponse,
|
||||
query_changes::{AddedItem, QueryChangesResponse},
|
||||
},
|
||||
object::{
|
||||
AnyId, JmapObject, JmapObjectId,
|
||||
calendar_event_notification::{
|
||||
CalendarEventNotificationGetResponse, CalendarEventNotificationObject,
|
||||
},
|
||||
},
|
||||
request::reference::ResultReference,
|
||||
};
|
||||
use compact_str::format_compact;
|
||||
use jmap_tools::{Element, JsonPointerItem, JsonPointerIter, Key, Null, Property, Value};
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
use types::{blob::BlobId, id::Id};
|
||||
|
||||
pub(crate) trait ResponsePtr {
|
||||
fn eval_jptr(&self, pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct EvalResults(Vec<EvalResult>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum EvalResult {
|
||||
Id(AnyId),
|
||||
Property(Cow<'static, str>),
|
||||
}
|
||||
|
||||
impl<T> ResponsePtr for Vec<T>
|
||||
where
|
||||
T: ResponsePtr,
|
||||
{
|
||||
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
|
||||
match pointer.next() {
|
||||
Some(JsonPointerItem::Number(n)) => {
|
||||
if let Some(v) = self.get(*n as usize) {
|
||||
v.eval_jptr(pointer, results);
|
||||
}
|
||||
}
|
||||
Some(JsonPointerItem::Wildcard | JsonPointerItem::Root) | None => {
|
||||
for v in self {
|
||||
v.eval_jptr(pointer.clone(), results);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx, P, E> ResponsePtr for Value<'ctx, P, E>
|
||||
where
|
||||
P: Property,
|
||||
E: Element<Property = P> + JmapObjectId,
|
||||
{
|
||||
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
|
||||
match pointer.next() {
|
||||
Some(JsonPointerItem::Key(key)) => {
|
||||
if let Some(key) = key.as_string_key()
|
||||
&& let Value::Object(map) = self
|
||||
&& let Some(v) = map.get(&Key::Borrowed(key))
|
||||
{
|
||||
v.eval_jptr(pointer, results);
|
||||
}
|
||||
}
|
||||
Some(JsonPointerItem::Number(n)) => match self {
|
||||
Value::Array(values) => {
|
||||
if let Some(v) = values.get(*n as usize) {
|
||||
v.eval_jptr(pointer, results);
|
||||
}
|
||||
}
|
||||
Value::Object(map) => {
|
||||
let n = Key::Owned(n.to_string());
|
||||
if let Some(v) = map.get(&n) {
|
||||
v.eval_jptr(pointer, results);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(JsonPointerItem::Wildcard) => match self {
|
||||
Value::Array(values) => {
|
||||
for v in values {
|
||||
v.eval_jptr(pointer.clone(), results);
|
||||
}
|
||||
}
|
||||
Value::Object(map) => {
|
||||
for v in map.values() {
|
||||
v.eval_jptr(pointer.clone(), results);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(JsonPointerItem::Root) | None => match self {
|
||||
Value::Element(e) => {
|
||||
if let Some(id) = e.as_any_id() {
|
||||
results.0.push(EvalResult::Id(id));
|
||||
}
|
||||
}
|
||||
Value::Array(list) => {
|
||||
for item in list {
|
||||
if let Value::Element(e) = item
|
||||
&& let Some(id) = e.as_any_id()
|
||||
{
|
||||
results.0.push(EvalResult::Id(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponsePtr for Id {
|
||||
fn eval_jptr(&self, _pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
|
||||
results.0.push(EvalResult::Id(AnyId::Id(*self)));
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponsePtr for BlobId {
|
||||
fn eval_jptr(&self, _pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
|
||||
results.0.push(EvalResult::Id(AnyId::BlobId(self.clone())));
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Property> ResponsePtr for PropertyWrapper<T> {
|
||||
fn eval_jptr(&self, _: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
|
||||
results.0.push(EvalResult::Property(self.0.to_cow()));
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: JmapObject> ResponsePtr for GetResponse<T> {
|
||||
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
|
||||
match pointer.next().and_then(|item| item.as_string_key()) {
|
||||
Some("list") => {
|
||||
self.list.eval_jptr(pointer, results);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: JmapObject> ResponsePtr for ChangesResponse<T> {
|
||||
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
|
||||
if let Some(property) = pointer.next().and_then(|item| item.as_string_key()) {
|
||||
hashify::fnc_map!(property.as_bytes(),
|
||||
"created" => {
|
||||
self.created.eval_jptr(pointer, results);
|
||||
},
|
||||
"updated" => {
|
||||
self.updated.eval_jptr(pointer, results);
|
||||
},
|
||||
"updatedProperties" => {
|
||||
if let Some(props) = &self.updated_properties {
|
||||
props.eval_jptr(pointer, results);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponsePtr for QueryResponse {
|
||||
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
|
||||
match pointer.next().and_then(|item| item.as_string_key()) {
|
||||
Some("ids") => {
|
||||
self.ids.eval_jptr(pointer, results);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponsePtr for QueryChangesResponse {
|
||||
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
|
||||
match pointer.next().and_then(|item| item.as_string_key()) {
|
||||
Some("added") => {
|
||||
self.added.eval_jptr(pointer, results);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponsePtr for AddedItem {
|
||||
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
|
||||
match pointer.next().and_then(|item| item.as_string_key()) {
|
||||
Some("id") => {
|
||||
results.0.push(EvalResult::Id(AnyId::Id(self.id)));
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponsePtr for CalendarEventNotificationGetResponse {
|
||||
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
|
||||
match pointer.next().and_then(|item| item.as_string_key()) {
|
||||
Some("list") => {
|
||||
self.list.eval_jptr(pointer, results);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponsePtr for CalendarEventNotificationObject {
|
||||
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
|
||||
match pointer.next().and_then(|item| item.as_string_key()) {
|
||||
Some("id") => {
|
||||
results.0.push(EvalResult::Id(AnyId::Id(self.id)));
|
||||
true
|
||||
}
|
||||
Some("calendarEventId") => {
|
||||
if let Some(id) = &self.calendar_event_id {
|
||||
results.0.push(EvalResult::Id(AnyId::Id(*id)));
|
||||
}
|
||||
true
|
||||
}
|
||||
Some("event") => {
|
||||
if let Some(event) = &self.event {
|
||||
event.0.eval_jptr(pointer, results);
|
||||
}
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponsePtr for GetAvailabilityResponse {
|
||||
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
|
||||
match pointer.next().and_then(|item| item.as_string_key()) {
|
||||
Some("list") => {
|
||||
self.list.eval_jptr(pointer, results);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponsePtr for BusyPeriod {
|
||||
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
|
||||
match pointer.next().and_then(|item| item.as_string_key()) {
|
||||
Some("event") => {
|
||||
if let Some(event) = &self.event {
|
||||
event.0.eval_jptr(pointer, results);
|
||||
}
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EvalResults {
|
||||
pub fn into_ids<T: TryFrom<AnyId>>(
|
||||
self,
|
||||
rr: &ResultReference,
|
||||
) -> impl Iterator<Item = trc::Result<T>> {
|
||||
self.0.into_iter().map(move |id| {
|
||||
if let EvalResult::Id(any_id) = id {
|
||||
T::try_from(any_id).map_err(|_| {
|
||||
trc::JmapEvent::InvalidResultReference
|
||||
.into_err()
|
||||
.details(format_compact!(
|
||||
"Failed to evaluate {rr} result reference: Invalid Id type."
|
||||
))
|
||||
})
|
||||
} else {
|
||||
Err(trc::JmapEvent::InvalidResultReference
|
||||
.into_err()
|
||||
.details(format_compact!(
|
||||
"Failed to evaluate {rr} result reference: Invalid Id type."
|
||||
)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn into_properties<T: Property + FromStr>(
|
||||
self,
|
||||
rr: &ResultReference,
|
||||
) -> impl Iterator<Item = trc::Result<T>> {
|
||||
self.0.into_iter().map(move |prop| {
|
||||
if let EvalResult::Property(prop) = prop {
|
||||
T::from_str(&prop).map_err(|_| {
|
||||
trc::JmapEvent::InvalidResultReference
|
||||
.into_err()
|
||||
.details(format_compact!(
|
||||
"Failed to evaluate {rr} result reference: Invalid property."
|
||||
))
|
||||
})
|
||||
} else {
|
||||
Err(trc::JmapEvent::InvalidResultReference
|
||||
.into_err()
|
||||
.details(format_compact!(
|
||||
"Failed to evaluate {rr} result reference: Invalid property."
|
||||
)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,944 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use compact_str::format_compact;
|
||||
use std::collections::HashMap;
|
||||
use utils::map::vec_map::VecMap;
|
||||
|
||||
pub mod eval;
|
||||
pub mod jsptr;
|
||||
pub mod resolve;
|
||||
|
||||
pub(crate) enum Graph<'x> {
|
||||
Some {
|
||||
child_id: &'x str,
|
||||
graph: &'x mut HashMap<String, Vec<String>>,
|
||||
},
|
||||
None,
|
||||
}
|
||||
|
||||
fn topological_sort<T>(
|
||||
create: &mut VecMap<String, T>,
|
||||
graph: HashMap<String, Vec<String>>,
|
||||
) -> trc::Result<VecMap<String, T>> {
|
||||
// Make sure all references exist
|
||||
for (from_id, to_ids) in graph.iter() {
|
||||
for to_id in to_ids {
|
||||
if !create.contains_key(to_id) {
|
||||
return Err(trc::JmapEvent::InvalidResultReference.into_err().details(
|
||||
format_compact!(
|
||||
"Invalid reference to non-existing object {to_id:?} from {from_id:?}"
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut sorted_create = VecMap::with_capacity(create.len());
|
||||
let mut it_stack = Vec::new();
|
||||
let keys = graph.keys().cloned().collect::<Vec<_>>();
|
||||
let mut it = keys.iter();
|
||||
|
||||
'main: loop {
|
||||
while let Some(from_id) = it.next() {
|
||||
if let Some(to_ids) = graph.get(from_id) {
|
||||
it_stack.push((it, from_id));
|
||||
if it_stack.len() > 1000 {
|
||||
return Err(trc::JmapEvent::InvalidArguments
|
||||
.into_err()
|
||||
.details("Cyclical references are not allowed."));
|
||||
}
|
||||
it = to_ids.iter();
|
||||
continue;
|
||||
} else if let Some((id, value)) = create.remove_entry(from_id) {
|
||||
sorted_create.append(id, value);
|
||||
if create.is_empty() {
|
||||
break 'main;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((prev_it, from_id)) = it_stack.pop() {
|
||||
it = prev_it;
|
||||
if let Some((id, value)) = create.remove_entry(from_id) {
|
||||
sorted_create.append(id, value);
|
||||
if create.is_empty() {
|
||||
break 'main;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add remaining items
|
||||
if !create.is_empty() {
|
||||
for (id, value) in std::mem::take(create) {
|
||||
sorted_create.append(id, value);
|
||||
}
|
||||
}
|
||||
Ok(sorted_create)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::references::Graph;
|
||||
use crate::references::eval::EvalObjectReferences;
|
||||
use crate::{
|
||||
method::{changes::ChangesResponse, get::GetResponse, query::QueryResponse},
|
||||
object::{
|
||||
email::{EmailProperty, EmailValue},
|
||||
mailbox::{MailboxProperty, MailboxValue},
|
||||
thread::{ThreadProperty, ThreadValue},
|
||||
},
|
||||
request::{
|
||||
Call, GetRequestMethod, Request, RequestMethod, SetRequestMethod,
|
||||
reference::{MaybeIdReference, MaybeResultReference},
|
||||
},
|
||||
response::{ChangesResponseMethod, GetResponseMethod, Response, ResponseMethod},
|
||||
};
|
||||
use jmap_tools::{Key, Map, Value};
|
||||
use std::collections::HashMap;
|
||||
use types::id::Id;
|
||||
|
||||
#[test]
|
||||
fn eval_value_references() {
|
||||
let request = Request::parse(
|
||||
br##"{
|
||||
"using":["urn:ietf:params:jmap:mail"],
|
||||
"methodCalls": [[ "Email/query", {
|
||||
"accountId": "a",
|
||||
"filter": { "inMailbox": "a" },
|
||||
"sort": [{ "property": "receivedAt", "isAscending": false }],
|
||||
"collapseThreads": true,
|
||||
"position": 0,
|
||||
"limit": 10,
|
||||
"calculateTotal": true
|
||||
}, "t0" ],
|
||||
[ "Email/get", {
|
||||
"accountId": "a",
|
||||
"#ids": {
|
||||
"resultOf": "t0",
|
||||
"name": "Email/query",
|
||||
"path": "/ids"
|
||||
},
|
||||
"properties": [ "threadId" ]
|
||||
}, "t1" ],
|
||||
[ "Thread/get", {
|
||||
"accountId": "a",
|
||||
"#ids": {
|
||||
"resultOf": "t1",
|
||||
"name": "Email/get",
|
||||
"path": "/list/*/threadId"
|
||||
}
|
||||
}, "t2" ],
|
||||
[ "Email/get", {
|
||||
"accountId": "a",
|
||||
"#ids": {
|
||||
"resultOf": "t2",
|
||||
"name": "Thread/get",
|
||||
"path": "/list/*/emailIds"
|
||||
},
|
||||
"properties": [ "from", "receivedAt", "subject" ]
|
||||
}, "t3" ]]
|
||||
}"##,
|
||||
100,
|
||||
1024 * 1024,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut response = Response::new(
|
||||
1234,
|
||||
request.created_ids.unwrap_or_default(),
|
||||
request.method_calls.len(),
|
||||
);
|
||||
|
||||
assert_eq!(request.method_calls.len(), 4);
|
||||
|
||||
for (test_num, mut call) in request.method_calls.into_iter().enumerate() {
|
||||
match test_num {
|
||||
0 => {
|
||||
response.method_responses.push(Call {
|
||||
id: call.id,
|
||||
name: call.name,
|
||||
method: ResponseMethod::Query(QueryResponse {
|
||||
account_id: Id::new(1),
|
||||
query_state: Default::default(),
|
||||
can_calculate_changes: Default::default(),
|
||||
position: Default::default(),
|
||||
ids: vec![Id::new(4), Id::new(5)],
|
||||
total: Default::default(),
|
||||
limit: Default::default(),
|
||||
}),
|
||||
});
|
||||
}
|
||||
1 => {
|
||||
response.resolve_references(&mut call.method).unwrap();
|
||||
match call.method {
|
||||
RequestMethod::Get(GetRequestMethod::Email(req)) => {
|
||||
assert_eq!(
|
||||
req.ids,
|
||||
Some(MaybeResultReference::Value(vec![
|
||||
MaybeIdReference::Id(Id::new(4)),
|
||||
MaybeIdReference::Id(Id::new(5))
|
||||
]))
|
||||
);
|
||||
}
|
||||
_ => panic!("Expected Email Get Request"),
|
||||
}
|
||||
response.method_responses.push(Call {
|
||||
id: call.id,
|
||||
name: call.name,
|
||||
method: ResponseMethod::Get(GetResponseMethod::Email(GetResponse {
|
||||
account_id: Id::new(1).into(),
|
||||
state: Default::default(),
|
||||
list: vec![
|
||||
Value::Object(Map::from(vec![(
|
||||
Key::Property(EmailProperty::ThreadId),
|
||||
Value::Element(EmailValue::Id(Id::new(9))),
|
||||
)])),
|
||||
Value::Object(Map::from(vec![(
|
||||
Key::Property(EmailProperty::ThreadId),
|
||||
Value::Element(EmailValue::Id(Id::new(10))),
|
||||
)])),
|
||||
],
|
||||
not_found: Default::default(),
|
||||
})),
|
||||
});
|
||||
}
|
||||
2 => {
|
||||
response.resolve_references(&mut call.method).unwrap();
|
||||
match call.method {
|
||||
RequestMethod::Get(GetRequestMethod::Thread(req)) => {
|
||||
assert_eq!(
|
||||
req.ids,
|
||||
Some(MaybeResultReference::Value(vec![
|
||||
MaybeIdReference::Id(Id::new(9)),
|
||||
MaybeIdReference::Id(Id::new(10))
|
||||
]))
|
||||
);
|
||||
}
|
||||
_ => panic!("Expected Thread Get Request"),
|
||||
}
|
||||
response.method_responses.push(Call {
|
||||
id: call.id,
|
||||
name: call.name,
|
||||
method: ResponseMethod::Get(GetResponseMethod::Thread(GetResponse {
|
||||
account_id: Id::new(1).into(),
|
||||
state: Default::default(),
|
||||
list: vec![
|
||||
Value::Object(Map::from(vec![(
|
||||
Key::Property(ThreadProperty::EmailIds),
|
||||
Value::Array(vec![
|
||||
Value::Element(ThreadValue::Id(Id::new(100))),
|
||||
Value::Element(ThreadValue::Id(Id::new(101))),
|
||||
]),
|
||||
)])),
|
||||
Value::Object(Map::from(vec![(
|
||||
Key::Property(ThreadProperty::EmailIds),
|
||||
Value::Array(vec![
|
||||
Value::Element(ThreadValue::Id(Id::new(102))),
|
||||
Value::Element(ThreadValue::Id(Id::new(103))),
|
||||
]),
|
||||
)])),
|
||||
],
|
||||
not_found: Default::default(),
|
||||
})),
|
||||
});
|
||||
}
|
||||
3 => {
|
||||
response.resolve_references(&mut call.method).unwrap();
|
||||
match call.method {
|
||||
RequestMethod::Get(GetRequestMethod::Email(req)) => {
|
||||
assert_eq!(
|
||||
req.ids,
|
||||
Some(MaybeResultReference::Value(vec![
|
||||
MaybeIdReference::Id(Id::new(100)),
|
||||
MaybeIdReference::Id(Id::new(101)),
|
||||
MaybeIdReference::Id(Id::new(102)),
|
||||
MaybeIdReference::Id(Id::new(103)),
|
||||
]))
|
||||
);
|
||||
}
|
||||
_ => panic!("Expected Mailbox Get Request"),
|
||||
}
|
||||
}
|
||||
_ => panic!("Unexpected invocation {}", test_num),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_property_references() {
|
||||
let request = Request::parse(
|
||||
br##"{
|
||||
"using":["urn:ietf:params:jmap:mail"],
|
||||
"methodCalls": [
|
||||
["Mailbox/changes",{
|
||||
"accountId":"s",
|
||||
"sinceState":"srxqk071myhgkyay"
|
||||
},"0"],
|
||||
["Mailbox/get",{
|
||||
"accountId":"s",
|
||||
"#ids":{"name":"Mailbox/changes","path":"/created","resultOf":"0"}
|
||||
},"1"],
|
||||
["Mailbox/get",{
|
||||
"accountId":"s",
|
||||
"#ids":{"name":"Mailbox/changes","path":"/updated","resultOf":"0"},
|
||||
"#properties":{"name":"Mailbox/changes","path":"/updatedProperties","resultOf":"0"}
|
||||
},"2"]
|
||||
]
|
||||
}"##,
|
||||
100,
|
||||
1024 * 1024,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut response = Response::new(
|
||||
1234,
|
||||
request.created_ids.unwrap_or_default(),
|
||||
request.method_calls.len(),
|
||||
);
|
||||
|
||||
assert_eq!(request.method_calls.len(), 3);
|
||||
|
||||
for (test_num, mut call) in request.method_calls.into_iter().enumerate() {
|
||||
match test_num {
|
||||
0 => {
|
||||
response.method_responses.push(Call {
|
||||
id: call.id,
|
||||
name: call.name,
|
||||
method: ResponseMethod::Changes(ChangesResponseMethod::Mailbox(Box::new(
|
||||
ChangesResponse {
|
||||
account_id: Id::new(1),
|
||||
old_state: Default::default(),
|
||||
new_state: Default::default(),
|
||||
has_more_changes: Default::default(),
|
||||
created: Default::default(),
|
||||
updated: vec![Id::new(2), Id::new(3)],
|
||||
destroyed: Default::default(),
|
||||
updated_properties: Some(vec![
|
||||
MailboxProperty::Name.into(),
|
||||
MailboxProperty::ParentId.into(),
|
||||
]),
|
||||
},
|
||||
))),
|
||||
});
|
||||
}
|
||||
1 => {
|
||||
response.resolve_references(&mut call.method).unwrap();
|
||||
match call.method {
|
||||
RequestMethod::Get(GetRequestMethod::Mailbox(req)) => {
|
||||
assert_eq!(req.ids, Some(MaybeResultReference::Value(vec![])));
|
||||
}
|
||||
_ => panic!("Expected Mailbox Get Request"),
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
response.resolve_references(&mut call.method).unwrap();
|
||||
match call.method {
|
||||
RequestMethod::Get(GetRequestMethod::Mailbox(req)) => {
|
||||
assert_eq!(
|
||||
req.ids,
|
||||
Some(MaybeResultReference::Value(vec![
|
||||
MaybeIdReference::Id(Id::new(2)),
|
||||
MaybeIdReference::Id(Id::new(3))
|
||||
]))
|
||||
);
|
||||
}
|
||||
_ => panic!("Expected Mailbox Get Request"),
|
||||
}
|
||||
}
|
||||
_ => panic!("Unexpected invocation {}", test_num),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_create_references() {
|
||||
let request = Request::parse(
|
||||
br##"{
|
||||
"using": [
|
||||
"urn:ietf:params:jmap:core",
|
||||
"urn:ietf:params:jmap:mail"
|
||||
],
|
||||
"methodCalls": [
|
||||
[
|
||||
"Mailbox/set",
|
||||
{
|
||||
"accountId": "b",
|
||||
"create": {
|
||||
"a": {
|
||||
"name": "Folder a",
|
||||
"parentId": "#b"
|
||||
},
|
||||
"b": {
|
||||
"name": "Folder b",
|
||||
"parentId": "#c"
|
||||
},
|
||||
"c": {
|
||||
"name": "Folder c",
|
||||
"parentId": "#d"
|
||||
},
|
||||
"d": {
|
||||
"name": "Folder d",
|
||||
"parentId": "#e"
|
||||
},
|
||||
"e": {
|
||||
"name": "Folder e",
|
||||
"parentId": "#f"
|
||||
},
|
||||
"f": {
|
||||
"name": "Folder f",
|
||||
"parentId": "#g"
|
||||
},
|
||||
"g": {
|
||||
"name": "Folder g",
|
||||
"parentId": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"fulltree"
|
||||
],
|
||||
[
|
||||
"Mailbox/set",
|
||||
{
|
||||
"accountId": "b",
|
||||
"create": {
|
||||
"a1": {
|
||||
"name": "Folder a1",
|
||||
"parentId": null
|
||||
},
|
||||
"b2": {
|
||||
"name": "Folder b2",
|
||||
"parentId": "#a1"
|
||||
},
|
||||
"c3": {
|
||||
"name": "Folder c3",
|
||||
"parentId": "#a1"
|
||||
},
|
||||
"d4": {
|
||||
"name": "Folder d4",
|
||||
"parentId": "#b2"
|
||||
},
|
||||
"e5": {
|
||||
"name": "Folder e5",
|
||||
"parentId": "#b2"
|
||||
},
|
||||
"f6": {
|
||||
"name": "Folder f6",
|
||||
"parentId": "#d4"
|
||||
},
|
||||
"g7": {
|
||||
"name": "Folder g7",
|
||||
"parentId": "#e5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fulltree2"
|
||||
],
|
||||
[
|
||||
"Mailbox/set",
|
||||
{
|
||||
"accountId": "b",
|
||||
"create": {
|
||||
"z": {
|
||||
"name": "Folder Z",
|
||||
"parentId": "#x"
|
||||
},
|
||||
"y": {
|
||||
"name": null
|
||||
},
|
||||
"x": {
|
||||
"name": "Folder X"
|
||||
}
|
||||
}
|
||||
},
|
||||
"xyz"
|
||||
],
|
||||
[
|
||||
"Mailbox/set",
|
||||
{
|
||||
"accountId": "b",
|
||||
"create": {
|
||||
"a": {
|
||||
"name": "Folder a",
|
||||
"parentId": "#b"
|
||||
},
|
||||
"b": {
|
||||
"name": "Folder b",
|
||||
"parentId": "#c"
|
||||
},
|
||||
"c": {
|
||||
"name": "Folder c",
|
||||
"parentId": "#d"
|
||||
},
|
||||
"d": {
|
||||
"name": "Folder d",
|
||||
"parentId": "#a"
|
||||
}
|
||||
}
|
||||
},
|
||||
"circular"
|
||||
]
|
||||
]
|
||||
}"##,
|
||||
100,
|
||||
1024 * 1024,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let response = Response::new(
|
||||
1234,
|
||||
request.created_ids.unwrap_or_default(),
|
||||
request.method_calls.len(),
|
||||
);
|
||||
|
||||
for (test_num, mut call) in request.method_calls.into_iter().enumerate() {
|
||||
match response.resolve_references(&mut call.method) {
|
||||
Ok(_) => assert!(
|
||||
(0..3).contains(&test_num),
|
||||
"Unexpected invocation {}",
|
||||
test_num
|
||||
),
|
||||
Err(err) => {
|
||||
assert_eq!(test_num, 3);
|
||||
assert!(
|
||||
err.matches(trc::EventType::Jmap(trc::JmapEvent::InvalidArguments)),
|
||||
"{:?}",
|
||||
err
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let RequestMethod::Set(SetRequestMethod::Mailbox(request)) = call.method {
|
||||
if test_num == 0 {
|
||||
assert_eq!(
|
||||
request
|
||||
.create
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|b| b.0)
|
||||
.collect::<Vec<_>>(),
|
||||
["g", "f", "e", "d", "c", "b", "a"]
|
||||
.iter()
|
||||
.map(|i| i.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
} else if test_num == 1 {
|
||||
let mut pending_ids = vec!["a1", "b2", "d4", "e5", "f6", "c3", "g7"];
|
||||
|
||||
for (id, _) in request.create.as_ref().unwrap() {
|
||||
match id.as_str() {
|
||||
"a1" => (),
|
||||
"b2" | "c3" => assert!(!pending_ids.contains(&"a1")),
|
||||
"d4" | "e5" => assert!(!pending_ids.contains(&"b2")),
|
||||
"f6" => assert!(!pending_ids.contains(&"d4")),
|
||||
"g7" => assert!(!pending_ids.contains(&"e5")),
|
||||
_ => panic!("Unexpected ID"),
|
||||
}
|
||||
pending_ids.retain(|i| i != id);
|
||||
}
|
||||
|
||||
if !pending_ids.is_empty() {
|
||||
panic!(
|
||||
"Unexpected order: {:?}",
|
||||
request
|
||||
.create
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|b| b.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
} else if test_num == 2 {
|
||||
assert_eq!(
|
||||
request
|
||||
.create
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|b| b.0)
|
||||
.collect::<Vec<_>>(),
|
||||
["x", "z", "y"]
|
||||
.iter()
|
||||
.map(|i| i.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
panic!("Expected Set Mailbox Request");
|
||||
}
|
||||
}
|
||||
|
||||
let request = Request::parse(
|
||||
br##"{
|
||||
"using": [
|
||||
"urn:ietf:params:jmap:core",
|
||||
"urn:ietf:params:jmap:mail"
|
||||
],
|
||||
"methodCalls": [
|
||||
[
|
||||
"Mailbox/set",
|
||||
{
|
||||
"accountId": "b",
|
||||
"create": {
|
||||
"a": {
|
||||
"name": "a",
|
||||
"parentId": "#x"
|
||||
},
|
||||
"b": {
|
||||
"name": "b",
|
||||
"parentId": "#y"
|
||||
},
|
||||
"c": {
|
||||
"name": "c",
|
||||
"parentId": "#z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ref1"
|
||||
],
|
||||
[
|
||||
"Mailbox/set",
|
||||
{
|
||||
"accountId": "b",
|
||||
"create": {
|
||||
"a1": {
|
||||
"name": "a1",
|
||||
"parentId": "#a"
|
||||
},
|
||||
"b2": {
|
||||
"name": "b2",
|
||||
"parentId": "#b"
|
||||
},
|
||||
"c3": {
|
||||
"name": "c3",
|
||||
"parentId": "#c"
|
||||
}
|
||||
}
|
||||
},
|
||||
"red2"
|
||||
]
|
||||
],
|
||||
"createdIds": {
|
||||
"x": "b",
|
||||
"y": "c",
|
||||
"z": "d"
|
||||
}
|
||||
}"##,
|
||||
1024,
|
||||
1024 * 1024,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut response = Response::new(
|
||||
1234,
|
||||
request.created_ids.unwrap_or_default(),
|
||||
request.method_calls.len(),
|
||||
);
|
||||
|
||||
let mut invocations = request.method_calls.into_iter();
|
||||
let mut call = invocations.next().unwrap();
|
||||
response.resolve_references(&mut call.method).unwrap();
|
||||
|
||||
if let RequestMethod::Set(SetRequestMethod::Mailbox(request)) = call.method {
|
||||
let create = request
|
||||
.create
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(p, v)| {
|
||||
(
|
||||
p.as_str(),
|
||||
v.as_object()
|
||||
.unwrap()
|
||||
.get(&Key::Property(MailboxProperty::ParentId))
|
||||
.unwrap(),
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
assert_eq!(
|
||||
*create.get("a").unwrap(),
|
||||
&Value::Element(MailboxValue::Id(Id::new(1)))
|
||||
);
|
||||
assert_eq!(
|
||||
*create.get("b").unwrap(),
|
||||
&Value::Element(MailboxValue::Id(Id::new(2)))
|
||||
);
|
||||
assert_eq!(
|
||||
*create.get("c").unwrap(),
|
||||
&Value::Element(MailboxValue::Id(Id::new(3)))
|
||||
);
|
||||
} else {
|
||||
panic!("Expected Mailbox Set Request");
|
||||
}
|
||||
|
||||
response
|
||||
.created_ids
|
||||
.insert("a".to_string(), Id::new(5).into());
|
||||
response
|
||||
.created_ids
|
||||
.insert("b".to_string(), Id::new(6).into());
|
||||
response
|
||||
.created_ids
|
||||
.insert("c".to_string(), Id::new(7).into());
|
||||
|
||||
let mut call = invocations.next().unwrap();
|
||||
response.resolve_references(&mut call.method).unwrap();
|
||||
|
||||
if let RequestMethod::Set(SetRequestMethod::Mailbox(request)) = call.method {
|
||||
let create = request
|
||||
.create
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(p, v)| {
|
||||
(
|
||||
p.as_str(),
|
||||
v.as_object()
|
||||
.unwrap()
|
||||
.get(&Key::Property(MailboxProperty::ParentId))
|
||||
.unwrap(),
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
assert_eq!(
|
||||
*create.get("a1").unwrap(),
|
||||
&Value::Element(MailboxValue::Id(Id::new(5)))
|
||||
);
|
||||
assert_eq!(
|
||||
*create.get("b2").unwrap(),
|
||||
&Value::Element(MailboxValue::Id(Id::new(6)))
|
||||
);
|
||||
assert_eq!(
|
||||
*create.get("c3").unwrap(),
|
||||
&Value::Element(MailboxValue::Id(Id::new(7)))
|
||||
);
|
||||
} else {
|
||||
panic!("Expected Mailbox Set Request");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_nested_element_ref() {
|
||||
let mut created_ids = HashMap::new();
|
||||
created_ids.insert("server-1".to_string(), Id::new(42).into());
|
||||
let response = Response::new(0, created_ids, 0);
|
||||
|
||||
let mut value: Value<'_, MailboxProperty, MailboxValue> = Value::Object(Map::from(vec![
|
||||
(
|
||||
Key::Property(MailboxProperty::Name),
|
||||
Value::Str("inbox".into()),
|
||||
),
|
||||
(
|
||||
Key::Property(MailboxProperty::ParentId),
|
||||
Value::Object(Map::from(vec![(
|
||||
Key::Property(MailboxProperty::ParentId),
|
||||
Value::Element(MailboxValue::IdReference("server-1".into())),
|
||||
)])),
|
||||
),
|
||||
]));
|
||||
|
||||
value
|
||||
.eval_object_references(&response, &mut Graph::None, 0, 5, true)
|
||||
.unwrap();
|
||||
|
||||
let nested = value
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get(&Key::Property(MailboxProperty::ParentId))
|
||||
.unwrap()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get(&Key::Property(MailboxProperty::ParentId))
|
||||
.unwrap();
|
||||
assert_eq!(nested, &Value::Element(MailboxValue::Id(Id::new(42))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_array_element_ref() {
|
||||
let mut created_ids = HashMap::new();
|
||||
created_ids.insert("a".to_string(), Id::new(1).into());
|
||||
created_ids.insert("b".to_string(), Id::new(2).into());
|
||||
let response = Response::new(0, created_ids, 0);
|
||||
|
||||
let mut value: Value<'_, MailboxProperty, MailboxValue> = Value::Object(Map::from(vec![(
|
||||
Key::Property(MailboxProperty::ParentId),
|
||||
Value::Array(vec![
|
||||
Value::Element(MailboxValue::IdReference("a".into())),
|
||||
Value::Element(MailboxValue::IdReference("b".into())),
|
||||
]),
|
||||
)]));
|
||||
|
||||
value
|
||||
.eval_object_references(&response, &mut Graph::None, 0, 5, true)
|
||||
.unwrap();
|
||||
|
||||
let arr = value
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get(&Key::Property(MailboxProperty::ParentId))
|
||||
.unwrap()
|
||||
.as_array()
|
||||
.unwrap();
|
||||
assert_eq!(arr.len(), 2);
|
||||
assert_eq!(arr[0], Value::Element(MailboxValue::Id(Id::new(1))));
|
||||
assert_eq!(arr[1], Value::Element(MailboxValue::Id(Id::new(2))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_nested_array_of_objects_with_element_ref() {
|
||||
let mut created_ids = HashMap::new();
|
||||
created_ids.insert("a".to_string(), Id::new(7).into());
|
||||
let response = Response::new(0, created_ids, 0);
|
||||
|
||||
let mut value: Value<'_, MailboxProperty, MailboxValue> = Value::Object(Map::from(vec![(
|
||||
Key::Property(MailboxProperty::ParentId),
|
||||
Value::Array(vec![Value::Object(Map::from(vec![(
|
||||
Key::Property(MailboxProperty::ParentId),
|
||||
Value::Element(MailboxValue::IdReference("a".into())),
|
||||
)]))]),
|
||||
)]));
|
||||
|
||||
value
|
||||
.eval_object_references(&response, &mut Graph::None, 0, 5, true)
|
||||
.unwrap();
|
||||
|
||||
let resolved = value
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get(&Key::Property(MailboxProperty::ParentId))
|
||||
.unwrap()
|
||||
.as_array()
|
||||
.unwrap()[0]
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get(&Key::Property(MailboxProperty::ParentId))
|
||||
.unwrap();
|
||||
assert_eq!(resolved, &Value::Element(MailboxValue::Id(Id::new(7))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_graph_collects_nested_ref() {
|
||||
let response = Response::new(0, HashMap::new(), 0);
|
||||
let mut graph_map: HashMap<String, Vec<String>> = HashMap::new();
|
||||
let child_id = "outer".to_string();
|
||||
|
||||
let mut value: Value<'_, MailboxProperty, MailboxValue> = Value::Object(Map::from(vec![(
|
||||
Key::Property(MailboxProperty::ParentId),
|
||||
Value::Object(Map::from(vec![(
|
||||
Key::Property(MailboxProperty::ParentId),
|
||||
Value::Element(MailboxValue::IdReference("inner".into())),
|
||||
)])),
|
||||
)]));
|
||||
|
||||
{
|
||||
let mut graph = Graph::Some {
|
||||
child_id: &child_id,
|
||||
graph: &mut graph_map,
|
||||
};
|
||||
value
|
||||
.eval_object_references(&response, &mut graph, 0, 5, true)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(graph_map.get("outer"), Some(&vec!["inner".to_string()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_unresolved_nested_ref_errors_without_graph() {
|
||||
let response = Response::new(0, HashMap::new(), 0);
|
||||
|
||||
let mut value: Value<'_, MailboxProperty, MailboxValue> = Value::Object(Map::from(vec![(
|
||||
Key::Property(MailboxProperty::ParentId),
|
||||
Value::Object(Map::from(vec![(
|
||||
Key::Property(MailboxProperty::ParentId),
|
||||
Value::Element(MailboxValue::IdReference("missing".into())),
|
||||
)])),
|
||||
)]));
|
||||
|
||||
let err = value
|
||||
.eval_object_references(&response, &mut Graph::None, 0, 5, true)
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
err.matches(trc::EventType::Jmap(trc::JmapEvent::InvalidResultReference)),
|
||||
"{:?}",
|
||||
err
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_depth_limit_blocks_walk_into_inner_object() {
|
||||
let mut created_ids = HashMap::new();
|
||||
created_ids.insert("inner".to_string(), Id::new(99).into());
|
||||
let response = Response::new(0, created_ids, 0);
|
||||
|
||||
let mut value: Value<'_, MailboxProperty, MailboxValue> = Value::Object(Map::from(vec![(
|
||||
Key::Property(MailboxProperty::ParentId),
|
||||
Value::Object(Map::from(vec![(
|
||||
Key::Property(MailboxProperty::ParentId),
|
||||
Value::Object(Map::from(vec![(
|
||||
Key::Property(MailboxProperty::ParentId),
|
||||
Value::Element(MailboxValue::IdReference("inner".into())),
|
||||
)])),
|
||||
)])),
|
||||
)]));
|
||||
|
||||
value
|
||||
.eval_object_references(&response, &mut Graph::None, 0, 2, true)
|
||||
.unwrap();
|
||||
|
||||
let deepest = value
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get(&Key::Property(MailboxProperty::ParentId))
|
||||
.unwrap()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get(&Key::Property(MailboxProperty::ParentId))
|
||||
.unwrap()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get(&Key::Property(MailboxProperty::ParentId))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
deepest,
|
||||
&Value::Element(MailboxValue::IdReference("inner".into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_depth_limit_substitutes_element_at_max_depth() {
|
||||
let mut created_ids = HashMap::new();
|
||||
created_ids.insert("inner".to_string(), Id::new(99).into());
|
||||
let response = Response::new(0, created_ids, 0);
|
||||
|
||||
let mut value: Value<'_, MailboxProperty, MailboxValue> = Value::Object(Map::from(vec![(
|
||||
Key::Property(MailboxProperty::ParentId),
|
||||
Value::Object(Map::from(vec![(
|
||||
Key::Property(MailboxProperty::ParentId),
|
||||
Value::Element(MailboxValue::IdReference("inner".into())),
|
||||
)])),
|
||||
)]));
|
||||
|
||||
value
|
||||
.eval_object_references(&response, &mut Graph::None, 0, 2, true)
|
||||
.unwrap();
|
||||
|
||||
let resolved = value
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get(&Key::Property(MailboxProperty::ParentId))
|
||||
.unwrap()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get(&Key::Property(MailboxProperty::ParentId))
|
||||
.unwrap();
|
||||
assert_eq!(resolved, &Value::Element(MailboxValue::Id(Id::new(99))));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
error::set::SetError,
|
||||
method::{
|
||||
copy::CopyRequest,
|
||||
get::GetRequest,
|
||||
import::ImportEmailRequest,
|
||||
parse::ParseRequest,
|
||||
search_snippet::GetSearchSnippetRequest,
|
||||
set::{SetRequest, SetResponse},
|
||||
upload::{BlobUploadRequest, DataSourceObject},
|
||||
},
|
||||
object::{AnyId, JmapObject, JmapObjectId},
|
||||
references::{Graph, eval::EvalObjectReferences, topological_sort},
|
||||
request::{
|
||||
CopyRequestMethod, GetRequestMethod, MaybeInvalid, ParseRequestMethod, RequestMethod,
|
||||
SetRequestMethod,
|
||||
reference::{MaybeIdReference, MaybeResultReference},
|
||||
},
|
||||
response::Response,
|
||||
};
|
||||
use compact_str::format_compact;
|
||||
use jmap_tools::{Element, Key, Property, Value};
|
||||
use std::collections::HashMap;
|
||||
use types::id::Id;
|
||||
|
||||
impl Response<'_> {
|
||||
pub fn resolve_references(&self, request: &mut RequestMethod) -> trc::Result<()> {
|
||||
match request {
|
||||
RequestMethod::Get(request) => match request {
|
||||
GetRequestMethod::Email(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::Mailbox(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::Thread(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::Identity(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::EmailSubmission(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::PushSubscription(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::Sieve(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::VacationResponse(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::Principal(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::Quota(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::Blob(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::AddressBook(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::ContactCard(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::FileNode(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::ShareNotification(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::Calendar(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::CalendarEvent(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::CalendarEventNotification(request) => {
|
||||
request.resolve_references(self)?
|
||||
}
|
||||
GetRequestMethod::ParticipantIdentity(request) => {
|
||||
request.resolve_references(self)?
|
||||
}
|
||||
GetRequestMethod::PrincipalAvailability(_) => (),
|
||||
GetRequestMethod::Registry(request) => request.resolve_references(self)?,
|
||||
},
|
||||
RequestMethod::Set(request) => match request {
|
||||
SetRequestMethod::Email(request) => request.resolve_references(self, 2, false)?,
|
||||
SetRequestMethod::Mailbox(request) => request.resolve_references(self, 1, false)?,
|
||||
SetRequestMethod::Identity(request) => {
|
||||
request.resolve_references(self, 1, false)?
|
||||
}
|
||||
SetRequestMethod::EmailSubmission(request) => {
|
||||
request.resolve_references(self, 1, false)?
|
||||
}
|
||||
SetRequestMethod::PushSubscription(request) => {
|
||||
request.resolve_references(self, 1, false)?
|
||||
}
|
||||
SetRequestMethod::Sieve(request) => request.resolve_references(self, 1, false)?,
|
||||
SetRequestMethod::VacationResponse(request) => {
|
||||
request.resolve_references(self, 1, false)?
|
||||
}
|
||||
SetRequestMethod::AddressBook(request) => {
|
||||
request.resolve_references(self, 1, false)?
|
||||
}
|
||||
SetRequestMethod::ContactCard(request) => {
|
||||
request.resolve_references(self, 1, false)?
|
||||
}
|
||||
SetRequestMethod::FileNode(request) => {
|
||||
request.resolve_references(self, 1, false)?
|
||||
}
|
||||
SetRequestMethod::ShareNotification(request) => {
|
||||
request.resolve_references(self, 1, false)?
|
||||
}
|
||||
SetRequestMethod::Calendar(request) => {
|
||||
request.resolve_references(self, 1, false)?
|
||||
}
|
||||
SetRequestMethod::CalendarEvent(request) => {
|
||||
request.resolve_references(self, 1, false)?
|
||||
}
|
||||
SetRequestMethod::CalendarEventNotification(request) => {
|
||||
request.resolve_references(self, 1, false)?
|
||||
}
|
||||
SetRequestMethod::ParticipantIdentity(request) => {
|
||||
request.resolve_references(self, 1, false)?
|
||||
}
|
||||
SetRequestMethod::Registry(request) => request.resolve_references(self, 5, true)?,
|
||||
},
|
||||
RequestMethod::Copy(request) => match request {
|
||||
CopyRequestMethod::Email(request) => request.resolve_references(self, 1, false)?,
|
||||
CopyRequestMethod::CalendarEvent(request) => {
|
||||
request.resolve_references(self, 1, false)?
|
||||
}
|
||||
CopyRequestMethod::ContactCard(request) => {
|
||||
request.resolve_references(self, 1, false)?
|
||||
}
|
||||
CopyRequestMethod::FileNode(request) => {
|
||||
request.resolve_references(self, 1, false)?
|
||||
}
|
||||
CopyRequestMethod::Blob(_) => (),
|
||||
},
|
||||
RequestMethod::ImportEmail(request) => request.resolve_references(self)?,
|
||||
RequestMethod::SearchSnippet(request) => request.resolve_references(self)?,
|
||||
RequestMethod::UploadBlob(request) => request.resolve_references(self)?,
|
||||
RequestMethod::Parse(request) => match request {
|
||||
ParseRequestMethod::Email(request) => request.resolve_references(self)?,
|
||||
ParseRequestMethod::ContactCard(request) => request.resolve_references(self)?,
|
||||
ParseRequestMethod::CalendarEvent(request) => request.resolve_references(self)?,
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ResolveCreatedReference<P, E>
|
||||
where
|
||||
P: Property,
|
||||
E: Element<Property = P> + JmapObjectId,
|
||||
{
|
||||
fn get_created_id(&self, id_ref: &str) -> Option<AnyId>;
|
||||
|
||||
fn resolve_self_references(
|
||||
&self,
|
||||
value: &mut Value<'_, P, E>,
|
||||
depth: usize,
|
||||
eval_strings: bool,
|
||||
) -> Result<(), SetError<P>> {
|
||||
match value {
|
||||
Value::Object(obj) if eval_strings && depth < 5 => {
|
||||
for (key, value) in obj.as_mut_vec() {
|
||||
if let Some(id) = key
|
||||
.as_string_key()
|
||||
.and_then(|k| k.strip_prefix('#'))
|
||||
.and_then(|id_ref| self.get_created_id(id_ref))
|
||||
{
|
||||
*key = Key::Owned(match id {
|
||||
AnyId::Id(id) => id.to_string(),
|
||||
AnyId::BlobId(id) => id.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
if matches!(value, Value::Object(_) | Value::Array(_)) {
|
||||
self.resolve_self_references(value, depth + 1, eval_strings)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Element(element) => {
|
||||
if let Some(id_ref) = element.as_id_ref() {
|
||||
if let Some(id) = self.get_created_id(id_ref) {
|
||||
if !element.try_set_id(id) {
|
||||
return Err(SetError::invalid_properties()
|
||||
.with_description("Id reference points to invalid type."));
|
||||
}
|
||||
} else {
|
||||
return Err(SetError::not_found()
|
||||
.with_description(format!("Id reference {id_ref:?} not found.")));
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Array(items) if depth < 5 => {
|
||||
for item in items {
|
||||
self.resolve_self_references(item, depth + 1, eval_strings)?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait ResolveReference {
|
||||
fn resolve_references(&mut self, response: &Response<'_>) -> trc::Result<()>;
|
||||
}
|
||||
|
||||
pub(crate) trait ResolveSetReference {
|
||||
fn resolve_references(
|
||||
&mut self,
|
||||
response: &Response<'_>,
|
||||
max_depth: usize,
|
||||
eval_strings: bool,
|
||||
) -> trc::Result<()>;
|
||||
}
|
||||
|
||||
impl<T: JmapObject> ResolveReference for GetRequest<T> {
|
||||
fn resolve_references(&mut self, response: &Response<'_>) -> trc::Result<()> {
|
||||
// Resolve id references
|
||||
match &mut self.ids {
|
||||
Some(MaybeResultReference::Reference(reference)) => {
|
||||
self.ids = Some(MaybeResultReference::Value(
|
||||
response
|
||||
.eval_result_references(reference)?
|
||||
.into_ids::<T::Id>(reference)
|
||||
.map(|f| f.map(MaybeIdReference::Id))
|
||||
.collect::<Result<_, _>>()?,
|
||||
));
|
||||
}
|
||||
Some(MaybeResultReference::Value(ids)) => {
|
||||
for id in ids {
|
||||
if let MaybeIdReference::Reference(reference) = id {
|
||||
if let Some(resolved_id) = response
|
||||
.created_ids
|
||||
.get(reference)
|
||||
.cloned()
|
||||
.and_then(|v| T::Id::try_from(v).ok())
|
||||
{
|
||||
*id = MaybeIdReference::Id(resolved_id);
|
||||
} else {
|
||||
return Err(trc::JmapEvent::InvalidResultReference.into_err().details(
|
||||
format_compact!(
|
||||
"Id reference {reference:?} does not exist or is invalid."
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// Resolve properties references
|
||||
if let Some(MaybeResultReference::Reference(reference)) = &self.properties {
|
||||
self.properties = Some(MaybeResultReference::Value(
|
||||
response
|
||||
.eval_result_references(reference)?
|
||||
.into_properties::<T::Property>(reference)
|
||||
.map(|f| f.map(MaybeInvalid::Value))
|
||||
.collect::<Result<_, _>>()?,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x, T: JmapObject> ResolveSetReference for SetRequest<'x, T> {
|
||||
fn resolve_references(
|
||||
&mut self,
|
||||
response: &Response<'_>,
|
||||
max_depth: usize,
|
||||
eval_strings: bool,
|
||||
) -> trc::Result<()> {
|
||||
// Resolve create references
|
||||
if let Some(create) = &mut self.create {
|
||||
let mut graph = HashMap::with_capacity(create.len());
|
||||
for (id, obj) in create.iter_mut() {
|
||||
obj.eval_object_references(
|
||||
response,
|
||||
&mut Graph::Some {
|
||||
child_id: &*id,
|
||||
graph: &mut graph,
|
||||
},
|
||||
0,
|
||||
max_depth,
|
||||
eval_strings,
|
||||
)?;
|
||||
}
|
||||
|
||||
// Perform topological sort
|
||||
if !graph.is_empty() {
|
||||
self.create = topological_sort(create, graph)?.into();
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve update references
|
||||
if let Some(update) = &mut self.update {
|
||||
for obj in update.values_mut() {
|
||||
obj.eval_object_references(response, &mut Graph::None, 0, max_depth, eval_strings)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve destroy references
|
||||
if let Some(MaybeResultReference::Reference(reference)) = &self.destroy {
|
||||
self.destroy = Some(MaybeResultReference::Value(
|
||||
response
|
||||
.eval_result_references(reference)?
|
||||
.into_ids::<Id>(reference)
|
||||
.map(|f| f.map(MaybeInvalid::Value))
|
||||
.collect::<Result<_, _>>()?,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x, T: JmapObject> ResolveSetReference for CopyRequest<'x, T> {
|
||||
fn resolve_references(
|
||||
&mut self,
|
||||
response: &Response<'_>,
|
||||
max_depth: usize,
|
||||
eval_strings: bool,
|
||||
) -> trc::Result<()> {
|
||||
// Resolve create references
|
||||
for (id, obj) in self.create.iter_mut() {
|
||||
obj.eval_object_references(response, &mut Graph::None, 0, max_depth, eval_strings)?;
|
||||
|
||||
if let MaybeIdReference::Reference(ir) = id {
|
||||
*id = MaybeIdReference::Id(response.eval_id_reference(ir)?);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: JmapObject> ResolveReference for ParseRequest<T> {
|
||||
fn resolve_references(&mut self, response: &Response<'_>) -> trc::Result<()> {
|
||||
// Resolve blobId references
|
||||
for id in self.blob_ids.iter_mut() {
|
||||
if let MaybeIdReference::Reference(ir) = id {
|
||||
*id = MaybeIdReference::Id(response.eval_blob_id_reference(ir)?);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolveReference for ImportEmailRequest {
|
||||
fn resolve_references(&mut self, response: &Response<'_>) -> trc::Result<()> {
|
||||
// Resolve email mailbox references
|
||||
for email in self.emails.values_mut() {
|
||||
match &mut email.mailbox_ids {
|
||||
MaybeResultReference::Reference(reference) => {
|
||||
email.mailbox_ids = MaybeResultReference::Value(
|
||||
response
|
||||
.eval_result_references(reference)?
|
||||
.into_ids::<Id>(reference)
|
||||
.map(|f| f.map(MaybeIdReference::Id))
|
||||
.collect::<Result<_, _>>()?,
|
||||
);
|
||||
}
|
||||
MaybeResultReference::Value(values) => {
|
||||
for value in values {
|
||||
if let MaybeIdReference::Reference(ir) = value {
|
||||
*value = MaybeIdReference::Id(response.eval_id_reference(ir)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolveReference for GetSearchSnippetRequest {
|
||||
fn resolve_references(&mut self, response: &Response<'_>) -> trc::Result<()> {
|
||||
// Resolve emailIds references
|
||||
if let MaybeResultReference::Reference(reference) = &self.email_ids {
|
||||
self.email_ids = MaybeResultReference::Value(
|
||||
response
|
||||
.eval_result_references(reference)?
|
||||
.into_ids::<Id>(reference)
|
||||
.map(|f| f.map(MaybeInvalid::Value))
|
||||
.collect::<Result<_, _>>()?,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolveReference for BlobUploadRequest {
|
||||
fn resolve_references(&mut self, response: &Response<'_>) -> trc::Result<()> {
|
||||
let mut graph = HashMap::with_capacity(self.create.len());
|
||||
for (create_id, object) in self.create.iter_mut() {
|
||||
for data in &mut object.data {
|
||||
if let DataSourceObject::Id { id, .. } = data
|
||||
&& let MaybeIdReference::Reference(parent_id) = id
|
||||
{
|
||||
match response.created_ids.get(parent_id) {
|
||||
Some(AnyId::BlobId(blob_id)) => {
|
||||
*id = MaybeIdReference::Id(blob_id.clone());
|
||||
}
|
||||
Some(_) => {
|
||||
return Err(trc::JmapEvent::InvalidResultReference.into_err().details(
|
||||
format_compact!(
|
||||
"Id reference {parent_id:?} points to invalid type."
|
||||
),
|
||||
));
|
||||
}
|
||||
None => {
|
||||
graph
|
||||
.entry(create_id.to_string())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(parent_id.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform topological sort
|
||||
if !graph.is_empty() {
|
||||
self.create = topological_sort(&mut self.create, graph)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ResolveCreatedReference<T::Property, T::Element> for SetResponse<T>
|
||||
where
|
||||
T: JmapObject,
|
||||
{
|
||||
fn get_created_id(&self, id_ref: &str) -> Option<AnyId> {
|
||||
self.created
|
||||
.get(id_ref)
|
||||
.and_then(|v| v.as_object())
|
||||
.and_then(|v| v.get(&Key::Property(T::ID_PROPERTY)))
|
||||
.and_then(|v| v.as_element())
|
||||
.and_then(|v| v.as_any_id())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user