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:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
+280
View File
@@ -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(())
}
}
+335
View File
@@ -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."
)))
}
})
}
}
+944
View File
@@ -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))));
}
}
+433
View File
@@ -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())
}
}