/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
usesuper::request::{
BatchPoster, InfoCollections, InfoConfiguration, PostQueue, PostResponse, PostResponseHandler,
}; usesuper::token; usecrate::bso::{IncomingBso, IncomingEncryptedBso, OutgoingBso, OutgoingEncryptedBso}; usecrate::engine::{CollectionPost, CollectionRequest}; usecrate::error::{self, Error, ErrorResponse}; usecrate::record_types::MetaGlobalRecord; usecrate::{CollectionName, Guid, ServerTimestamp}; use serde_json::Value; use std::str::FromStr; use std::sync::atomic::{AtomicU32, Ordering}; use url::Url; use viaduct::{
header_names::{self, AUTHORIZATION},
Method, Request, Response,
};
/// A response from a GET request on a Sync15StorageClient, encapsulating all /// the variants users of this client needs to care about. #[derive(Debug, Clone)] pubenum Sync15ClientResponse<T> {
Success {
status: u16,
record: T,
last_modified: ServerTimestamp,
route: String,
},
Error(ErrorResponse),
}
impl<T> Sync15ClientResponse<T> { pubfn from_response(resp: Response, backoff_listener: &BackoffListener) -> error::Result<Self> where for<'a> T: serde::de::Deserialize<'a>,
{ let route: String = resp.url.path().into(); // Android seems to respect retry_after even on success requests, so we // will too if it's present. This also lets us handle both backoff-like // properties in the same place. let retry_after = resp
.headers
.get(header_names::RETRY_AFTER)
.and_then(parse_seconds);
let backoff = resp
.headers
.get(header_names::X_WEAVE_BACKOFF)
.and_then(parse_seconds);
Ok(if resp.is_success() { let record: T = resp.json()?; let last_modified = resp
.headers
.get(header_names::X_LAST_MODIFIED)
.and_then(|s| ServerTimestamp::from_str(s).ok())
.ok_or(Error::MissingServerTimestamp)?;
log::info!( "Successful request to \"{}\", incoming x-last-modified={:?}",
route,
last_modified
);
Sync15ClientResponse::Success {
status: resp.status,
record,
last_modified,
route,
}
} else { let status = resp.status;
log::info!("Request \"{}\" was an error (status={})", route, status); match status { 404 => Sync15ClientResponse::Error(ErrorResponse::NotFound { route }), 401 => Sync15ClientResponse::Error(ErrorResponse::Unauthorized { route }), 412 => Sync15ClientResponse::Error(ErrorResponse::PreconditionFailed { route }), 500..=600 => {
Sync15ClientResponse::Error(ErrorResponse::ServerError { route, status })
}
_ => Sync15ClientResponse::Error(ErrorResponse::RequestFailed { route, status }),
}
})
}
pubfn create_storage_error(self) -> Error { let inner = matchself {
Sync15ClientResponse::Success { status, route, .. } => { // This should never happen as callers are expected to have // already special-cased this response, so warn if it does. // (or maybe we could panic?)
log::warn!("Converting success response into an error");
ErrorResponse::RequestFailed { status, route }
}
Sync15ClientResponse::Error(e) => e,
};
Error::StorageHttpError(inner)
}
}
/// A trait containing the methods required to run through the setup state /// machine. This is factored out into a separate trait to make mocking /// easier. pubtrait SetupStorageClient { fn fetch_info_configuration(&self) -> error::Result<Sync15ClientResponse<InfoConfiguration>>; fn fetch_info_collections(&self) -> error::Result<Sync15ClientResponse<InfoCollections>>; fn fetch_meta_global(&self) -> error::Result<Sync15ClientResponse<MetaGlobalRecord>>; fn fetch_crypto_keys(&self) -> error::Result<Sync15ClientResponse<IncomingEncryptedBso>>;
// meta/global is a clear-text Bso (ie, there's a String `payload` which has a MetaGlobalRecord) // We don't use the 'content' helpers here because we want json errors to be fatal here // (ie, we don't need tombstones and can't just skip a malformed record) type IncMetaGlobalBso = IncomingBso; type OutMetaGlobalBso = OutgoingBso;
// TODO: probably want a builder-like API to do collection requests (e.g. something // that occupies roughly the same conceptual role as the Collection class in desktop) fn build_request(&self, method: Method, url: Url) -> error::Result<Request> { self.authorized(Request::new(method, url).header(header_names::ACCEPT, "application/json")?)
}
fn relative_storage_request<P, T>(
&self,
method: Method,
relative_path: P,
) -> error::Result<Sync15ClientResponse<T>> where
P: AsRef<str>, for<'a> T: serde::de::Deserialize<'a>,
{ let s = self.tsc.api_endpoint()? + "/"; let url = Url::parse(&s)?.join(relative_path.as_ref())?; self.exec_request(self.build_request(method, url)?, false)
}
fn put<P, B>(
&self,
relative_path: P,
xius: ServerTimestamp,
body: &B,
) -> error::Result<ServerTimestamp> where
P: AsRef<str>,
B: serde::ser::Serialize,
{ let s = self.tsc.api_endpoint()? + "/"; let url = Url::parse(&s)?.join(relative_path.as_ref())?;
let req = self
.build_request(Method::Put, url)?
.json(body)
.header(header_names::X_IF_UNMODIFIED_SINCE, format!("{}", xius))?;
let resp = self.exec_request::<Value>(req, true)?; // Note: we pass `true` for require_success, so this panic never happens. iflet Sync15ClientResponse::Success { last_modified, .. } = resp {
Ok(last_modified)
} else {
unreachable!("Error returned exec_request when `require_success` was true");
}
}
fn build_collection_url(mut base_url: Url, collection: CollectionName) -> error::Result<Url> {
base_url
.path_segments_mut()
.map_err(|_| Error::UnacceptableUrl("Storage server URL is not a base".to_string()))?
.extend(&["storage", &collection]);
// This is strange but just accessing query_pairs_mut makes you have // a trailing question mark on your url. I don't think anything bad // would happen here, but I don't know, and also, it looks dumb so // I'd rather not have it. if base_url.query() == Some("") {
base_url.set_query(None);
}
Ok(base_url)
}
fn build_collection_request_url(mut base_url: Url, r: CollectionRequest) -> error::Result<Url> { letmut pairs = base_url.query_pairs_mut(); if r.full {
pairs.append_pair("full", "1");
} iflet Some(ids) = &r.ids { // Most ids are 12 characters, and we comma separate them, so 13. letmut buf = String::with_capacity(ids.len() * 13); for (i, id) in ids.iter().enumerate() { if i > 0 {
buf.push(',');
}
buf.push_str(id.as_str());
}
pairs.append_pair("ids", &buf);
} iflet Some(ts) = r.older {
pairs.append_pair("older", &ts.to_string());
} iflet Some(ts) = r.newer {
pairs.append_pair("newer", &ts.to_string());
} iflet Some(l) = r.limit {
pairs.append_pair("sort", l.order.as_str());
pairs.append_pair("limit", &l.num.to_string());
}
pairs.finish();
drop(pairs);
build_collection_url(base_url, r.collection)
}
#[cfg(test)] mod test { usesuper::*; #[test] fn test_send() { fn ensure_send<T: Send>() {} // Compile will fail if not send.
ensure_send::<Sync15StorageClient>();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.