Anki/rslib/src/sync/http.rs
RumovZ c521753057
Refactor error handling (#2136)
* Add crate snafu

* Replace all inline structs in AnkiError

* Derive Snafu on AnkiError

* Use snafu for card type errors

* Use snafu whatever error for InvalidInput

* Use snafu for NotFoundError and improve message

* Use snafu for FileIoError to attach context

Remove IoError.
Add some context-attaching helpers to replace code returning bare
io::Errors.

* Add more context-attaching io helpers

* Add message, context and backtrace to new snafus

* Utilize error context and backtrace on frontend

* Rename LocalizedError -> BackendError.
* Remove DocumentedError.
* Have all backend exceptions inherit BackendError.

* Rename localized(_description) -> message

* Remove accidentally committed experimental trait

* invalid_input_context -> ok_or_invalid

* ensure_valid_input! -> require!

* Always return `Err` from `invalid_input!`

Instead of a Result to unwrap, the macro accepts a source error now.

* new_tempfile_in_parent -> new_tempfile_in_parent_of

* ok_or_not_found -> or_not_found

* ok_or_invalid -> or_invalid

* Add crate convert_case

* Use unqualified lowercase type name

* Remove uses of snafu::ensure

* Allow public construction of InvalidInputErrors (dae)

Needed to port the AnkiDroid changes.

* Make into_protobuf() public (dae)

Also required for AnkiDroid. Not sure why it worked previously - possible
bug in older Rust version?
2022-10-21 18:02:12 +10:00

124 lines
4.2 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use super::{Chunk, Graves, SanityCheckCounts, UnchunkedChanges};
use crate::{io::read_file, pb::sync_server_method_request::Method, prelude::*};
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub enum SyncRequest {
HostKey(HostKeyRequest),
Meta(MetaRequest),
Start(StartRequest),
ApplyGraves(ApplyGravesRequest),
ApplyChanges(ApplyChangesRequest),
Chunk,
ApplyChunk(ApplyChunkRequest),
#[serde(rename = "sanityCheck2")]
SanityCheck(SanityCheckRequest),
Finish,
Abort,
#[serde(rename = "upload")]
FullUpload(PathBuf),
#[serde(rename = "download")]
FullDownload,
}
impl SyncRequest {
/// Return method name and payload bytes.
pub(crate) fn into_method_and_data(self) -> Result<(&'static str, Vec<u8>)> {
use serde_json::to_vec;
Ok(match self {
SyncRequest::HostKey(v) => ("hostKey", to_vec(&v)?),
SyncRequest::Meta(v) => ("meta", to_vec(&v)?),
SyncRequest::Start(v) => ("start", to_vec(&v)?),
SyncRequest::ApplyGraves(v) => ("applyGraves", to_vec(&v)?),
SyncRequest::ApplyChanges(v) => ("applyChanges", to_vec(&v)?),
SyncRequest::Chunk => ("chunk", b"{}".to_vec()),
SyncRequest::ApplyChunk(v) => ("applyChunk", to_vec(&v)?),
SyncRequest::SanityCheck(v) => ("sanityCheck2", to_vec(&v)?),
SyncRequest::Finish => ("finish", b"{}".to_vec()),
SyncRequest::Abort => ("abort", b"{}".to_vec()),
SyncRequest::FullUpload(v) => {
// fixme: stream in the data instead, in a different call
("upload", read_file(&v)?)
}
SyncRequest::FullDownload => ("download", b"{}".to_vec()),
})
}
pub(crate) fn from_method_and_data(method: Method, data: Vec<u8>) -> Result<Self> {
use serde_json::from_slice;
Ok(match method {
Method::HostKey => SyncRequest::HostKey(from_slice(&data)?),
Method::Meta => SyncRequest::Meta(from_slice(&data)?),
Method::Start => SyncRequest::Start(from_slice(&data)?),
Method::ApplyGraves => SyncRequest::ApplyGraves(from_slice(&data)?),
Method::ApplyChanges => SyncRequest::ApplyChanges(from_slice(&data)?),
Method::Chunk => SyncRequest::Chunk,
Method::ApplyChunk => SyncRequest::ApplyChunk(from_slice(&data)?),
Method::SanityCheck => SyncRequest::SanityCheck(from_slice(&data)?),
Method::Finish => SyncRequest::Finish,
Method::Abort => SyncRequest::Abort,
Method::FullUpload => {
let path = PathBuf::from(String::from_utf8(data).expect("path was not in utf8"));
SyncRequest::FullUpload(path)
}
Method::FullDownload => SyncRequest::FullDownload,
})
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct HostKeyRequest {
#[serde(rename = "u")]
pub username: String,
#[serde(rename = "p")]
pub password: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct HostKeyResponse {
pub key: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct MetaRequest {
#[serde(rename = "v")]
pub sync_version: u8,
#[serde(rename = "cv")]
pub client_version: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct StartRequest {
#[serde(rename = "minUsn")]
pub client_usn: Usn,
#[serde(rename = "lnewer")]
pub local_is_newer: bool,
/// Unfortunately AnkiDroid is still using this
#[serde(rename = "graves", default)]
pub deprecated_client_graves: Option<Graves>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ApplyGravesRequest {
pub chunk: Graves,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ApplyChangesRequest {
pub changes: UnchunkedChanges,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ApplyChunkRequest {
pub chunk: Chunk,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct SanityCheckRequest {
pub client: SanityCheckCounts,
}