mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2025-09-08 04:54:57 +08:00
result references
This commit is contained in:
parent
633db406f7
commit
3250cbc443
20 changed files with 744 additions and 107 deletions
28
Cargo.toml
28
Cargo.toml
|
@ -1,18 +1,18 @@
|
|||
[package]
|
||||
name = "stalwart-jmap"
|
||||
description = "Stalwart JMAP Server"
|
||||
authors = [ "Stalwart Labs Ltd. <hello@stalw.art>"]
|
||||
repository = "https://github.com/stalwartlabs/jmap-server"
|
||||
homepage = "https://stalw.art/jmap"
|
||||
keywords = ["jmap", "email", "mail", "server"]
|
||||
categories = ["email"]
|
||||
license = "AGPL-3.0-only"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
#[package]
|
||||
#name = "stalwart-jmap"
|
||||
#description = "Stalwart JMAP Server"
|
||||
#authors = [ "Stalwart Labs Ltd. <hello@stalw.art>"]
|
||||
#repository = "https://github.com/stalwartlabs/jmap-server"
|
||||
#homepage = "https://stalw.art/jmap"
|
||||
#keywords = ["jmap", "email", "mail", "server"]
|
||||
#categories = ["email"]
|
||||
#license = "AGPL-3.0-only"
|
||||
#version = "0.3.0"
|
||||
#edition = "2021"
|
||||
#resolver = "2"
|
||||
|
||||
[lib]
|
||||
path = "crates/core/src/lib.rs"
|
||||
#[lib]
|
||||
#path = "crates/core/src/lib.rs"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
|
|
1
crates/core/src/api/mod.rs
Normal file
1
crates/core/src/api/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod request;
|
70
crates/core/src/api/request.rs
Normal file
70
crates/core/src/api/request.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
use protocol::{
|
||||
error::request::RequestError,
|
||||
method::{get, query},
|
||||
request::{Request, RequestMethod},
|
||||
response::{Response, ResponseMethod},
|
||||
};
|
||||
|
||||
use crate::JMAP;
|
||||
|
||||
impl JMAP {
|
||||
pub async fn handle_request(&self, bytes: &[u8]) -> Result<Response, RequestError> {
|
||||
let request = Request::parse(
|
||||
bytes,
|
||||
self.config.request_max_calls,
|
||||
self.config.request_max_size,
|
||||
)?;
|
||||
let mut response = Response::new(
|
||||
0,
|
||||
request.created_ids.unwrap_or_default(),
|
||||
request.method_calls.len(),
|
||||
);
|
||||
for mut call in request.method_calls {
|
||||
// Resolve result and id references
|
||||
if let Err(method_error) = response.resolve_references(&mut call.method) {
|
||||
response.push_response(call.id, method_error);
|
||||
continue;
|
||||
}
|
||||
|
||||
let method_response: ResponseMethod = match call.method {
|
||||
RequestMethod::Get(mut call) => match call.take_arguments() {
|
||||
get::RequestArguments::Email(arguments) => {
|
||||
self.email_get(call.with_arguments(arguments)).await.into()
|
||||
}
|
||||
get::RequestArguments::Mailbox => todo!(),
|
||||
get::RequestArguments::Thread => todo!(),
|
||||
get::RequestArguments::Identity => todo!(),
|
||||
get::RequestArguments::EmailSubmission => todo!(),
|
||||
get::RequestArguments::PushSubscription => todo!(),
|
||||
get::RequestArguments::SieveScript => todo!(),
|
||||
get::RequestArguments::VacationResponse => todo!(),
|
||||
get::RequestArguments::Principal => todo!(),
|
||||
},
|
||||
RequestMethod::Query(mut call) => match call.take_arguments() {
|
||||
query::RequestArguments::Email(arguments) => self
|
||||
.email_query(call.with_arguments(arguments))
|
||||
.await
|
||||
.into(),
|
||||
query::RequestArguments::Mailbox(_) => todo!(),
|
||||
query::RequestArguments::EmailSubmission => todo!(),
|
||||
query::RequestArguments::SieveScript => todo!(),
|
||||
query::RequestArguments::Principal => todo!(),
|
||||
},
|
||||
RequestMethod::Set(_) => todo!(),
|
||||
RequestMethod::Changes(_) => todo!(),
|
||||
RequestMethod::Copy(_) => todo!(),
|
||||
RequestMethod::CopyBlob(_) => todo!(),
|
||||
RequestMethod::ImportEmail(call) => self.email_import(call).await.into(),
|
||||
RequestMethod::ParseEmail(_) => todo!(),
|
||||
RequestMethod::QueryChanges(_) => todo!(),
|
||||
RequestMethod::SearchSnippet(_) => todo!(),
|
||||
RequestMethod::ValidateScript(_) => todo!(),
|
||||
RequestMethod::Echo(call) => call.into(),
|
||||
RequestMethod::Error(error) => error.into(),
|
||||
};
|
||||
response.push_response(call.id, method_response);
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
use mail_parser::Message;
|
||||
use protocol::{
|
||||
error::method::MethodError,
|
||||
method::get::GetResponse,
|
||||
method::get::{GetRequest, GetResponse},
|
||||
object::{email::GetArguments, Object},
|
||||
types::{blob::BlobId, collection::Collection, id::Id, property::Property, value::Value},
|
||||
types::{blob::BlobId, collection::Collection, property::Property, value::Value},
|
||||
};
|
||||
use store::ValueKey;
|
||||
|
||||
|
@ -14,12 +14,9 @@ use super::body::{ToBodyPart, TruncateBody};
|
|||
impl JMAP {
|
||||
pub async fn email_get(
|
||||
&self,
|
||||
account_id: u32,
|
||||
ids: Vec<Id>,
|
||||
properties: Option<Vec<Property>>,
|
||||
arguments: GetArguments,
|
||||
request: GetRequest<GetArguments>,
|
||||
) -> Result<GetResponse, MethodError> {
|
||||
let properties = properties.unwrap_or_else(|| {
|
||||
let properties = request.properties.map(|v| v.unwrap()).unwrap_or_else(|| {
|
||||
vec![
|
||||
Property::Id,
|
||||
Property::BlobId,
|
||||
|
@ -47,7 +44,7 @@ impl JMAP {
|
|||
Property::Attachments,
|
||||
]
|
||||
});
|
||||
let body_properties = arguments.body_properties.unwrap_or_else(|| {
|
||||
let body_properties = request.arguments.body_properties.unwrap_or_else(|| {
|
||||
vec![
|
||||
Property::PartId,
|
||||
Property::BlobId,
|
||||
|
@ -61,13 +58,20 @@ impl JMAP {
|
|||
Property::Location,
|
||||
]
|
||||
});
|
||||
let fetch_text_body_values = arguments.fetch_text_body_values.unwrap_or(false);
|
||||
let fetch_html_body_values = arguments.fetch_html_body_values.unwrap_or(false);
|
||||
let fetch_all_body_values = arguments.fetch_all_body_values.unwrap_or(false);
|
||||
let max_body_value_bytes = arguments.max_body_value_bytes.unwrap_or(0);
|
||||
let fetch_text_body_values = request.arguments.fetch_text_body_values.unwrap_or(false);
|
||||
let fetch_html_body_values = request.arguments.fetch_html_body_values.unwrap_or(false);
|
||||
let fetch_all_body_values = request.arguments.fetch_all_body_values.unwrap_or(false);
|
||||
let max_body_value_bytes = request.arguments.max_body_value_bytes.unwrap_or(0);
|
||||
|
||||
let ids = if let Some(ids) = request.ids.map(|v| v.unwrap()) {
|
||||
ids
|
||||
} else {
|
||||
let implement = "";
|
||||
todo!()
|
||||
};
|
||||
let account_id = request.account_id.document_id();
|
||||
let mut response = GetResponse {
|
||||
account_id: Some(account_id.into()),
|
||||
account_id: Some(request.account_id),
|
||||
state: self
|
||||
.store
|
||||
.get_last_change_id(account_id, Collection::Email)
|
||||
|
|
|
@ -10,6 +10,8 @@ impl JMAP {
|
|||
&self,
|
||||
request: ImportEmailRequest,
|
||||
) -> Result<ImportEmailResponse, MethodError> {
|
||||
for (id, email) in request.emails {}
|
||||
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,7 @@ use crate::JMAP;
|
|||
impl JMAP {
|
||||
pub async fn email_query(
|
||||
&self,
|
||||
request: QueryRequest,
|
||||
arguments: QueryArguments,
|
||||
request: QueryRequest<QueryArguments>,
|
||||
) -> Result<QueryResponse, MethodError> {
|
||||
let account_id = request.account_id.document_id();
|
||||
let mut filters = Vec::with_capacity(request.filter.len());
|
||||
|
@ -264,7 +263,7 @@ impl JMAP {
|
|||
request.anchor.map(|a| a.document_id()),
|
||||
request.anchor_offset.unwrap_or(0),
|
||||
ValueKey::new(account_id, Collection::Email, 0, Property::ThreadId).into(),
|
||||
arguments.collapse_threads.unwrap_or(false),
|
||||
request.arguments.collapse_threads.unwrap_or(false),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use store::{fts::Language, Store};
|
||||
|
||||
pub mod api;
|
||||
pub mod email;
|
||||
|
||||
pub struct JMAP {
|
||||
|
@ -10,6 +11,8 @@ pub struct JMAP {
|
|||
pub struct Config {
|
||||
pub default_language: Language,
|
||||
pub query_max_results: usize,
|
||||
pub request_max_size: usize,
|
||||
pub request_max_calls: usize,
|
||||
}
|
||||
|
||||
pub enum MaybeError {
|
||||
|
|
|
@ -11,11 +11,11 @@ use crate::{
|
|||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GetRequest {
|
||||
pub struct GetRequest<T> {
|
||||
pub account_id: Id,
|
||||
pub ids: Option<MaybeReference<Vec<Id>, ResultReference>>,
|
||||
pub properties: Option<MaybeReference<Vec<Property>, ResultReference>>,
|
||||
pub arguments: RequestArguments,
|
||||
pub arguments: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -45,7 +45,7 @@ pub struct GetResponse {
|
|||
pub not_found: Vec<Id>,
|
||||
}
|
||||
|
||||
impl JsonObjectParser for GetRequest {
|
||||
impl JsonObjectParser for GetRequest<RequestArguments> {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
|
@ -124,3 +124,18 @@ impl RequestPropertyParser for RequestArguments {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetRequest<RequestArguments> {
|
||||
pub fn take_arguments(&mut self) -> RequestArguments {
|
||||
std::mem::replace(&mut self.arguments, RequestArguments::Principal)
|
||||
}
|
||||
|
||||
pub fn with_arguments<T>(self, arguments: T) -> GetRequest<T> {
|
||||
GetRequest {
|
||||
arguments,
|
||||
account_id: self.account_id,
|
||||
ids: self.ids,
|
||||
properties: self.properties,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct QueryRequest {
|
||||
pub struct QueryRequest<T> {
|
||||
pub account_id: Id,
|
||||
pub filter: Vec<Filter>,
|
||||
pub sort: Option<Vec<Comparator>>,
|
||||
|
@ -18,7 +18,7 @@ pub struct QueryRequest {
|
|||
pub anchor_offset: Option<i32>,
|
||||
pub limit: Option<usize>,
|
||||
pub calculate_total: Option<bool>,
|
||||
pub arguments: RequestArguments,
|
||||
pub arguments: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
|
@ -138,7 +138,7 @@ pub enum RequestArguments {
|
|||
Principal,
|
||||
}
|
||||
|
||||
impl JsonObjectParser for QueryRequest {
|
||||
impl JsonObjectParser for QueryRequest<RequestArguments> {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
|
@ -666,3 +666,23 @@ impl Comparator {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl QueryRequest<RequestArguments> {
|
||||
pub fn take_arguments(&mut self) -> RequestArguments {
|
||||
std::mem::replace(&mut self.arguments, RequestArguments::Principal)
|
||||
}
|
||||
|
||||
pub fn with_arguments<T>(self, arguments: T) -> QueryRequest<T> {
|
||||
QueryRequest {
|
||||
arguments,
|
||||
account_id: self.account_id,
|
||||
filter: self.filter,
|
||||
sort: self.sort,
|
||||
position: self.position,
|
||||
anchor: self.anchor,
|
||||
anchor_offset: self.anchor_offset,
|
||||
limit: self.limit,
|
||||
calculate_total: self.calculate_total,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -246,23 +246,13 @@ impl JsonObjectParser for Object<SetValue> {
|
|||
Property::ParentId | Property::EmailId | Property::IdentityId => parser
|
||||
.next_token::<MaybeReference<Id, String>>()?
|
||||
.unwrap_string_or_null("")?
|
||||
.map(|id| match id {
|
||||
MaybeReference::Value(id) => SetValue::Value(Value::Id(id)),
|
||||
MaybeReference::Reference(r) => SetValue::Value(Value::Text(r)),
|
||||
})
|
||||
.map(SetValue::IdReference)
|
||||
.unwrap_or(SetValue::Value(Value::Null)),
|
||||
Property::MailboxIds => {
|
||||
if property.patch.is_empty() {
|
||||
SetValue::Value(Value::List(
|
||||
<SetValueMap<MaybeReference<Id, String>>>::parse(parser)?
|
||||
.values
|
||||
.into_iter()
|
||||
.map(|id| match id {
|
||||
MaybeReference::Value(id) => Value::Id(id),
|
||||
MaybeReference::Reference(r) => Value::Text(r),
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
SetValue::IdReferences(
|
||||
<SetValueMap<MaybeReference<Id, String>>>::parse(parser)?.values,
|
||||
)
|
||||
} else {
|
||||
property.patch.push(Value::Bool(bool::parse(parser)?));
|
||||
SetValue::Patch(property.patch)
|
||||
|
|
|
@ -235,7 +235,6 @@ impl<K: JsonObjectParser + Eq + Display, V: JsonObjectParser> JsonObjectParser
|
|||
Token::DictStart => {
|
||||
let mut map = VecMap::new();
|
||||
|
||||
parser.next_token::<Ignore>()?.assert(Token::DictStart)?;
|
||||
while {
|
||||
map.append(parser.next_dict_key()?, V::parse(parser)?);
|
||||
!parser.is_dict_end()?
|
||||
|
|
|
@ -35,6 +35,7 @@ impl<'x> Parser<'x> {
|
|||
}
|
||||
|
||||
pub fn error(&self, message: &str) -> Error {
|
||||
println!("{}", std::str::from_utf8(&self.bytes[self.pos..]).unwrap());
|
||||
format!("{message} at position {}.", self.pos).into()
|
||||
}
|
||||
|
||||
|
|
|
@ -14,10 +14,10 @@ use crate::{
|
|||
method::{
|
||||
changes::ChangesRequest,
|
||||
copy::{CopyBlobRequest, CopyRequest},
|
||||
get::GetRequest,
|
||||
get::{self, GetRequest},
|
||||
import::ImportEmailRequest,
|
||||
parse::ParseEmailRequest,
|
||||
query::QueryRequest,
|
||||
query::{self, QueryRequest},
|
||||
query_changes::QueryChangesRequest,
|
||||
search_snippet::GetSearchSnippetRequest,
|
||||
set::SetRequest,
|
||||
|
@ -50,7 +50,7 @@ pub struct RequestProperty {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum RequestMethod {
|
||||
Get(GetRequest),
|
||||
Get(GetRequest<get::RequestArguments>),
|
||||
Set(SetRequest),
|
||||
Changes(ChangesRequest),
|
||||
Copy(CopyRequest),
|
||||
|
@ -58,7 +58,7 @@ pub enum RequestMethod {
|
|||
ImportEmail(ImportEmailRequest),
|
||||
ParseEmail(ParseEmailRequest),
|
||||
QueryChanges(QueryChangesRequest),
|
||||
Query(QueryRequest),
|
||||
Query(QueryRequest<query::RequestArguments>),
|
||||
SearchSnippet(GetSearchSnippetRequest),
|
||||
ValidateScript(ValidateSieveScriptRequest),
|
||||
Echo(Echo),
|
||||
|
@ -88,6 +88,7 @@ impl JsonObjectParser for RequestProperty {
|
|||
continue 'outer;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Ok(RequestProperty { hash, is_ref })
|
||||
|
|
|
@ -22,6 +22,15 @@ pub enum MaybeReference<V, R> {
|
|||
Reference(R),
|
||||
}
|
||||
|
||||
impl<V, R> MaybeReference<V, R> {
|
||||
pub fn unwrap(self) -> V {
|
||||
match self {
|
||||
MaybeReference::Value(v) => v,
|
||||
MaybeReference::Reference(_) => panic!("unwrap() called on MaybeReference::Reference"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonObjectParser for ResultReference {
|
||||
fn parse(parser: &mut Parser) -> crate::parser::Result<Self>
|
||||
where
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
pub mod references;
|
||||
|
||||
use ahash::AHashMap;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
error::method::MethodError,
|
||||
method::{
|
||||
ahash_is_empty,
|
||||
changes::ChangesResponse,
|
||||
copy::{CopyBlobResponse, CopyResponse},
|
||||
get::GetResponse,
|
||||
|
@ -49,12 +49,11 @@ pub struct Response {
|
|||
pub session_state: u32,
|
||||
|
||||
#[serde(rename(deserialize = "createdIds"))]
|
||||
#[serde(skip_serializing_if = "ahash_is_empty")]
|
||||
pub created_ids: AHashMap<String, Id>,
|
||||
pub created_ids: HashMap<String, Id>,
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub fn new(session_state: u32, created_ids: AHashMap<String, Id>, capacity: usize) -> Self {
|
||||
pub fn new(session_state: u32, created_ids: HashMap<String, Id>, capacity: usize) -> Self {
|
||||
Response {
|
||||
session_state,
|
||||
created_ids,
|
||||
|
@ -158,3 +157,12 @@ impl From<ValidateSieveScriptResponse> for ResponseMethod {
|
|||
ResponseMethod::ValidateScript(validate_script)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<ResponseMethod>> From<Result<T, MethodError>> for ResponseMethod {
|
||||
fn from(result: Result<T, MethodError>) -> Self {
|
||||
match result {
|
||||
Ok(value) => value.into(),
|
||||
Err(error) => error.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use utils::map::vec_map::VecMap;
|
||||
|
||||
use crate::{
|
||||
error::method::MethodError,
|
||||
object::Object,
|
||||
request::{
|
||||
method::MethodFunction,
|
||||
reference::{MaybeReference, ResultReference},
|
||||
Request, RequestMethod,
|
||||
RequestMethod,
|
||||
},
|
||||
types::{
|
||||
id::Id,
|
||||
property::Property,
|
||||
value::{SetValue, Value},
|
||||
},
|
||||
types::{id::Id, pointer::JSONPointer, property::Property, value::Value},
|
||||
};
|
||||
|
||||
use super::{Response, ResponseMethod};
|
||||
|
@ -37,6 +46,69 @@ impl Response {
|
|||
}
|
||||
}
|
||||
RequestMethod::Set(request) => {
|
||||
// Resolve create references
|
||||
if let Some(create) = &mut request.create {
|
||||
let mut graph = HashMap::with_capacity(create.len());
|
||||
for (id, obj) in create.iter_mut() {
|
||||
self.eval_object_references(obj, Some((&*id, &mut graph)))?;
|
||||
}
|
||||
|
||||
// Perform topological sort
|
||||
if !graph.is_empty() {
|
||||
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(MethodError::InvalidArguments(
|
||||
"Cyclical references are not allowed.".to_string(),
|
||||
));
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
request.create = sorted_create.into();
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve update references
|
||||
if let Some(update) = &mut request.update {
|
||||
for obj in update.values_mut() {
|
||||
self.eval_object_references(obj, None)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve destroy references
|
||||
if let Some(MaybeReference::Reference(reference)) = &request.destroy {
|
||||
request.destroy = Some(MaybeReference::Value(
|
||||
|
@ -45,13 +117,39 @@ impl Response {
|
|||
));
|
||||
}
|
||||
}
|
||||
RequestMethod::Changes(_) => todo!(),
|
||||
RequestMethod::Copy(_) => todo!(),
|
||||
RequestMethod::CopyBlob(_) => todo!(),
|
||||
RequestMethod::ImportEmail(_) => todo!(),
|
||||
RequestMethod::ParseEmail(_) => todo!(),
|
||||
RequestMethod::QueryChanges(_) => todo!(),
|
||||
RequestMethod::Query(_) => todo!(),
|
||||
RequestMethod::Copy(request) => {
|
||||
// Resolve create references
|
||||
for (id, obj) in request.create.iter_mut() {
|
||||
self.eval_object_references(obj, None)?;
|
||||
if let MaybeReference::Reference(ir) = id {
|
||||
*id = MaybeReference::Value(self.eval_id_reference(ir)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
RequestMethod::ImportEmail(request) => {
|
||||
// Resolve email mailbox references
|
||||
for email in request.emails.values_mut() {
|
||||
match &mut email.mailbox_ids {
|
||||
Some(MaybeReference::Reference(rr)) => {
|
||||
email.mailbox_ids = Some(MaybeReference::Value(
|
||||
self.eval_result_references(rr)
|
||||
.unwrap_ids(rr)?
|
||||
.into_iter()
|
||||
.map(MaybeReference::Value)
|
||||
.collect(),
|
||||
));
|
||||
}
|
||||
Some(MaybeReference::Value(values)) => {
|
||||
for value in values {
|
||||
if let MaybeReference::Reference(ir) = value {
|
||||
*value = MaybeReference::Value(self.eval_id_reference(ir)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
RequestMethod::SearchSnippet(request) => {
|
||||
// Resolve emailIds references
|
||||
if let MaybeReference::Reference(reference) = &request.email_ids {
|
||||
|
@ -61,9 +159,7 @@ impl Response {
|
|||
);
|
||||
}
|
||||
}
|
||||
RequestMethod::ValidateScript(_) => todo!(),
|
||||
RequestMethod::Echo(_) => todo!(),
|
||||
RequestMethod::Error(_) => todo!(),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -138,6 +234,66 @@ impl Response {
|
|||
|
||||
EvalResult::Failed
|
||||
}
|
||||
|
||||
fn eval_id_reference(&self, ir: &str) -> Result<Id, MethodError> {
|
||||
if let Some(id) = self.created_ids.get(ir) {
|
||||
Ok(*id)
|
||||
} else {
|
||||
Err(MethodError::InvalidResultReference(format!(
|
||||
"Id reference {ir:?} not found."
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_object_references(
|
||||
&self,
|
||||
obj: &mut Object<SetValue>,
|
||||
mut graph: Option<(&str, &mut HashMap<String, Vec<String>>)>,
|
||||
) -> Result<(), MethodError> {
|
||||
for set_value in obj.properties.values_mut() {
|
||||
match set_value {
|
||||
SetValue::IdReference(MaybeReference::Reference(parent_id)) => {
|
||||
if let Some(id) = self.created_ids.get(parent_id) {
|
||||
*set_value = SetValue::Value(Value::Id(*id));
|
||||
} else if let Some((child_id, graph)) = &mut graph {
|
||||
graph
|
||||
.entry(child_id.to_string())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(parent_id.to_string());
|
||||
} else {
|
||||
return Err(MethodError::InvalidResultReference(format!(
|
||||
"Id reference {parent_id:?} not found."
|
||||
)));
|
||||
}
|
||||
}
|
||||
SetValue::IdReferences(id_refs) => {
|
||||
for id_ref in id_refs {
|
||||
if let MaybeReference::Reference(parent_id) = id_ref {
|
||||
if let Some(id) = self.created_ids.get(parent_id) {
|
||||
*id_ref = MaybeReference::Value(*id);
|
||||
} else if let Some((child_id, graph)) = &mut graph {
|
||||
graph
|
||||
.entry(child_id.to_string())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(parent_id.to_string());
|
||||
} else {
|
||||
return Err(MethodError::InvalidResultReference(format!(
|
||||
"Id reference {parent_id:?} not found."
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SetValue::ResultReference(rr) => {
|
||||
*set_value =
|
||||
SetValue::Value(self.eval_result_references(rr).unwrap_ids(rr)?.into());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl EvalResult {
|
||||
|
@ -183,3 +339,358 @@ impl EvalResult {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
error::method::MethodError,
|
||||
request::{Request, RequestMethod},
|
||||
response::Response,
|
||||
types::{
|
||||
id::Id,
|
||||
property::Property,
|
||||
value::{SetValue, Value},
|
||||
},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn eval_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!(matches!(err, MethodError::InvalidArguments(_)));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let RequestMethod::Set(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(request) = call.method {
|
||||
let create = request
|
||||
.create
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|(p, mut v)| (p, v.properties.remove(&Property::ParentId).unwrap()))
|
||||
.collect::<HashMap<_, _>>();
|
||||
assert_eq!(
|
||||
create.get("a").unwrap(),
|
||||
&SetValue::Value(Value::Id(Id::new(1)))
|
||||
);
|
||||
assert_eq!(
|
||||
create.get("b").unwrap(),
|
||||
&SetValue::Value(Value::Id(Id::new(2)))
|
||||
);
|
||||
assert_eq!(
|
||||
create.get("c").unwrap(),
|
||||
&SetValue::Value(Value::Id(Id::new(3)))
|
||||
);
|
||||
} else {
|
||||
panic!("Expected Mailbox Set Request");
|
||||
}
|
||||
|
||||
response.created_ids.insert("a".to_string(), Id::new(5));
|
||||
response.created_ids.insert("b".to_string(), Id::new(6));
|
||||
response.created_ids.insert("c".to_string(), Id::new(7));
|
||||
|
||||
let mut call = invocations.next().unwrap();
|
||||
response.resolve_references(&mut call.method).unwrap();
|
||||
|
||||
if let RequestMethod::Set(request) = call.method {
|
||||
let create = request
|
||||
.create
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|(p, mut v)| (p, v.properties.remove(&Property::ParentId).unwrap()))
|
||||
.collect::<HashMap<_, _>>();
|
||||
assert_eq!(
|
||||
create.get("a1").unwrap(),
|
||||
&SetValue::Value(Value::Id(Id::new(5)))
|
||||
);
|
||||
assert_eq!(
|
||||
create.get("b2").unwrap(),
|
||||
&SetValue::Value(Value::Id(Id::new(6)))
|
||||
);
|
||||
assert_eq!(
|
||||
create.get("c3").unwrap(),
|
||||
&SetValue::Value(Value::Id(Id::new(7)))
|
||||
);
|
||||
} else {
|
||||
panic!("Expected Mailbox Set Request");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::{
|
|||
error::method::MethodError,
|
||||
object::Object,
|
||||
parser::{json::Parser, Ignore, JsonObjectParser, Token},
|
||||
request::reference::ResultReference,
|
||||
request::reference::{MaybeReference, ResultReference},
|
||||
};
|
||||
|
||||
use super::{
|
||||
|
@ -38,10 +38,12 @@ pub enum Value {
|
|||
Null,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SetValue {
|
||||
Value(Value),
|
||||
Patch(Vec<Value>),
|
||||
IdReference(MaybeReference<Id, String>),
|
||||
IdReferences(Vec<MaybeReference<Id, String>>),
|
||||
ResultReference(ResultReference),
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
store = { path = "../crates/store" }
|
||||
store = { path = "../crates/store", features = ["test_mode"] }
|
||||
utils = { path = "/home/vagrant/code/utils" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -4,7 +4,7 @@ use store::ahash::AHashMap;
|
|||
|
||||
use store::{
|
||||
write::{BatchBuilder, F_CLEAR},
|
||||
BlobId, BlobKey, Store, BLOB_HASH_LEN,
|
||||
BlobHash, BlobKey, Store, BLOB_HASH_LEN,
|
||||
};
|
||||
|
||||
pub async fn test(db: Arc<Store>) {
|
||||
|
@ -13,8 +13,8 @@ pub async fn test(db: Arc<Store>) {
|
|||
let blob_1 = vec![b'a'; 1024];
|
||||
let blob_2 = vec![b'b'; 1024];
|
||||
|
||||
let blob_id_1 = BlobId::from(&blob_1[..]);
|
||||
let blob_id_2 = BlobId::from(&blob_2[..]);
|
||||
let blob_id_1 = BlobHash::from(&blob_1[..]);
|
||||
let blob_id_2 = BlobHash::from(&blob_2[..]);
|
||||
|
||||
// Insert the same blobs concurrently
|
||||
let handles = (1..=100)
|
||||
|
@ -91,13 +91,13 @@ pub async fn test(db: Arc<Store>) {
|
|||
}
|
||||
|
||||
struct BlobPurge {
|
||||
result: AHashMap<BlobId, (u32, u32)>,
|
||||
result: AHashMap<BlobHash, (u32, u32)>,
|
||||
link_count: u32,
|
||||
ephemeral_count: u32,
|
||||
id: [u8; BLOB_HASH_LEN],
|
||||
}
|
||||
|
||||
async fn get_all_blobs(store: &Store) -> AHashMap<BlobId, (u32, u32)> {
|
||||
async fn get_all_blobs(store: &Store) -> AHashMap<BlobHash, (u32, u32)> {
|
||||
let results = BlobPurge {
|
||||
result: AHashMap::new(),
|
||||
id: [0u8; BLOB_HASH_LEN],
|
||||
|
@ -122,7 +122,7 @@ async fn get_all_blobs(store: &Store) -> AHashMap<BlobId, (u32, u32)> {
|
|||
.iterate(results, from_key, to_key, false, true, move |b, k, v| {
|
||||
if !k.starts_with(&b.id) {
|
||||
if b.link_count != u32::MAX {
|
||||
let id = BlobId { hash: b.id };
|
||||
let id = BlobHash { hash: b.id };
|
||||
b.result.insert(id, (b.link_count, b.ephemeral_count));
|
||||
}
|
||||
b.link_count = 0;
|
||||
|
@ -142,7 +142,7 @@ async fn get_all_blobs(store: &Store) -> AHashMap<BlobId, (u32, u32)> {
|
|||
.unwrap();
|
||||
|
||||
if b.link_count != u32::MAX {
|
||||
let id = BlobId { hash: b.id };
|
||||
let id = BlobHash { hash: b.id };
|
||||
b.result.insert(id, (b.link_count, b.ephemeral_count));
|
||||
}
|
||||
|
||||
|
|
|
@ -26,12 +26,12 @@ use std::{
|
|||
time::Instant,
|
||||
};
|
||||
|
||||
use store::ahash::AHashMap;
|
||||
use store::{ahash::AHashMap, query::sort::Pagination};
|
||||
|
||||
use store::{
|
||||
fts::{builder::FtsIndexBuilder, Language},
|
||||
query::{Comparator, Filter},
|
||||
write::{BatchBuilder, F_INDEX, F_TOKENIZE, F_VALUE},
|
||||
write::{BatchBuilder, F_BITMAP, F_INDEX, F_VALUE},
|
||||
Store, ValueKey,
|
||||
};
|
||||
|
||||
|
@ -127,7 +127,7 @@ pub async fn test(db: Arc<Store>, do_insert: bool) {
|
|||
builder.value(
|
||||
field_id,
|
||||
field.to_lowercase(),
|
||||
F_VALUE | F_TOKENIZE,
|
||||
F_VALUE | F_BITMAP,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ pub async fn test(db: Arc<Store>, do_insert: bool) {
|
|||
builder.value(
|
||||
field_id,
|
||||
field.to_lowercase(),
|
||||
F_VALUE | F_INDEX | F_TOKENIZE,
|
||||
F_VALUE | F_INDEX | F_BITMAP,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -221,14 +221,14 @@ pub async fn test_filter(db: Arc<Store>) {
|
|||
let tests = [
|
||||
(
|
||||
vec![
|
||||
Filter::match_english(fields["title"], "water"),
|
||||
Filter::has_english_text(fields["title"], "water"),
|
||||
Filter::eq(fields["year"], 1979u32),
|
||||
],
|
||||
vec!["p11293"],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
Filter::match_english(fields["medium"], "gelatin"),
|
||||
Filter::has_english_text(fields["medium"], "gelatin"),
|
||||
Filter::gt(fields["year"], 2000u32),
|
||||
Filter::lt(fields["width"], 180u32),
|
||||
Filter::gt(fields["width"], 0u32),
|
||||
|
@ -236,19 +236,19 @@ pub async fn test_filter(db: Arc<Store>) {
|
|||
vec!["p79426", "p79427", "p79428", "p79429", "p79430"],
|
||||
),
|
||||
(
|
||||
vec![Filter::match_english(fields["title"], "'rustic bridge'")],
|
||||
vec![Filter::has_english_text(fields["title"], "'rustic bridge'")],
|
||||
vec!["d05503"],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
Filter::match_english(fields["title"], "'rustic'"),
|
||||
Filter::match_english(fields["title"], "study"),
|
||||
Filter::has_english_text(fields["title"], "'rustic'"),
|
||||
Filter::has_english_text(fields["title"], "study"),
|
||||
],
|
||||
vec!["d00399", "d05352"],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
Filter::has_keywords(fields["artist"], "mauro kunst"),
|
||||
Filter::has_text(fields["artist"], "mauro kunst", Language::None),
|
||||
Filter::has_keyword(fields["artistRole"], "artist"),
|
||||
Filter::Or,
|
||||
Filter::eq(fields["year"], 1969u32),
|
||||
|
@ -260,9 +260,9 @@ pub async fn test_filter(db: Arc<Store>) {
|
|||
(
|
||||
vec![
|
||||
Filter::Not,
|
||||
Filter::match_english(fields["medium"], "oil"),
|
||||
Filter::has_english_text(fields["medium"], "oil"),
|
||||
Filter::End,
|
||||
Filter::match_english(fields["creditLine"], "bequeath"),
|
||||
Filter::has_english_text(fields["creditLine"], "bequeath"),
|
||||
Filter::Or,
|
||||
Filter::And,
|
||||
Filter::ge(fields["year"], 1900u32),
|
||||
|
@ -285,7 +285,7 @@ pub async fn test_filter(db: Arc<Store>) {
|
|||
Filter::And,
|
||||
Filter::has_keyword(fields["artist"], "warhol"),
|
||||
Filter::Not,
|
||||
Filter::match_english(fields["title"], "'campbell'"),
|
||||
Filter::has_english_text(fields["title"], "'campbell'"),
|
||||
Filter::End,
|
||||
Filter::Not,
|
||||
Filter::Or,
|
||||
|
@ -303,12 +303,12 @@ pub async fn test_filter(db: Arc<Store>) {
|
|||
),
|
||||
(
|
||||
vec![
|
||||
Filter::match_english(fields["title"], "study"),
|
||||
Filter::match_english(fields["medium"], "paper"),
|
||||
Filter::match_english(fields["creditLine"], "'purchased'"),
|
||||
Filter::has_english_text(fields["title"], "study"),
|
||||
Filter::has_english_text(fields["medium"], "paper"),
|
||||
Filter::has_english_text(fields["creditLine"], "'purchased'"),
|
||||
Filter::Not,
|
||||
Filter::match_english(fields["title"], "'anatomical'"),
|
||||
Filter::match_english(fields["title"], "'for'"),
|
||||
Filter::has_english_text(fields["title"], "'anatomical'"),
|
||||
Filter::has_english_text(fields["title"], "'for'"),
|
||||
Filter::End,
|
||||
Filter::gt(fields["year"], 1900u32),
|
||||
Filter::gt(fields["acquisitionYear"], 2000u32),
|
||||
|
@ -326,10 +326,7 @@ pub async fn test_filter(db: Arc<Store>) {
|
|||
.sort(
|
||||
docset,
|
||||
vec![Comparator::ascending(fields["accession_number"])],
|
||||
0,
|
||||
0,
|
||||
None,
|
||||
0,
|
||||
Pagination::new(0, 0, None, 0, None, false),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -342,7 +339,7 @@ pub async fn test_filter(db: Arc<Store>) {
|
|||
.map(|document_id| ValueKey {
|
||||
account_id: 0,
|
||||
collection: COLLECTION_ID,
|
||||
document_id,
|
||||
document_id: document_id as u32,
|
||||
family: 0,
|
||||
field: fields["accession_number"],
|
||||
})
|
||||
|
@ -420,8 +417,13 @@ pub async fn test_sort(db: Arc<Store>) {
|
|||
for (filter, sort, expected_results) in tests {
|
||||
//println!("Running test: {:?}", sort);
|
||||
let docset = db.filter(0, COLLECTION_ID, filter).await.unwrap();
|
||||
|
||||
let sorted_docset = db
|
||||
.sort(docset, sort, expected_results.len(), 0, None, 0)
|
||||
.sort(
|
||||
docset,
|
||||
sort,
|
||||
Pagination::new(expected_results.len(), 0, None, 0, None, false),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -433,7 +435,7 @@ pub async fn test_sort(db: Arc<Store>) {
|
|||
.map(|document_id| ValueKey {
|
||||
account_id: 0,
|
||||
collection: COLLECTION_ID,
|
||||
document_id,
|
||||
document_id: document_id as u32,
|
||||
family: 0,
|
||||
field: fields["accession_number"],
|
||||
})
|
||||
|
|
Loading…
Add table
Reference in a new issue