feat(updater): improve validation and error messages, closes #3761 (#3780)

This commit is contained in:
Lucas Fernandes Nogueira
2022-03-27 16:48:57 -07:00
committed by GitHub
parent 3c4ee7c997
commit dbc2873e82
3 changed files with 310 additions and 89 deletions

View File

@@ -0,0 +1,5 @@
---
"tauri": patch
---
Improved the updater response validation and error messages.

View File

@@ -51,7 +51,7 @@ pub struct RemoteRelease {
/// Update short description
pub body: Option<String>,
/// Optional signature for the current platform
pub signature: Option<String>,
pub signature: String,
#[cfg(target_os = "windows")]
/// Optional: Windows only try to use elevated task
pub with_elevated_task: bool,
@@ -65,42 +65,47 @@ impl RemoteRelease {
let version = match release.get("version") {
Some(version) => version
.as_str()
.ok_or_else(|| {
Error::RemoteMetadata("Unable to extract `version` from remote server".into())
})?
.trim_start_matches('v')
.to_string(),
None => release
.get("name")
.ok_or_else(|| Error::RemoteMetadata("Release missing `name` and `version`".into()))?
.as_str()
.ok_or_else(|| {
Error::RemoteMetadata("Unable to extract `name` from remote server`".into())
})?
.ok_or_else(|| Error::InvalidResponseType("version", "string", version.clone()))?
.trim_start_matches('v')
.to_string(),
None => {
let name = release
.get("name")
.ok_or(Error::MissingResponseField("version or name"))?;
name
.as_str()
.ok_or_else(|| Error::InvalidResponseType("name", "string", name.clone()))?
.trim_start_matches('v')
.to_string()
}
};
// pub_date is required default is: `N/A` if not provided by the remote JSON
let date = release
.get("pub_date")
.and_then(|v| v.as_str())
.unwrap_or("N/A")
.to_string();
let date = if let Some(date) = release.get("pub_date") {
date
.as_str()
.map(|d| d.to_string())
.ok_or_else(|| Error::InvalidResponseType("pub_date", "string", date.clone()))?
} else {
"N/A".into()
};
// body is optional to build our update
let body = release
.get("notes")
.map(|notes| notes.as_str().unwrap_or("").to_string());
// signature is optional to build our update
let mut signature = release
.get("signature")
.map(|signature| signature.as_str().unwrap_or("").to_string());
let body = if let Some(notes) = release.get("notes") {
Some(
notes
.as_str()
.map(|n| n.to_string())
.ok_or_else(|| Error::InvalidResponseType("notes", "string", notes.clone()))?,
)
} else {
None
};
let download_url;
#[cfg(target_os = "windows")]
let with_elevated_task;
let signature;
match release.get("platforms") {
//
@@ -123,38 +128,53 @@ impl RemoteRelease {
// use provided signature if available
signature = current_target_data
.get("signature")
.map(|found_signature| found_signature.as_str().unwrap_or("").to_string());
.ok_or(Error::MissingResponseField("signature"))
.and_then(|signature| {
signature
.as_str()
.ok_or_else(|| Error::InvalidResponseType("signature", "string", signature.clone()))
})?;
// Download URL is required
download_url = current_target_data
let url = current_target_data
.get("url")
.ok_or_else(|| Error::RemoteMetadata("Release missing `url`".into()))?
.ok_or(Error::MissingResponseField("url"))?;
download_url = url
.as_str()
.ok_or_else(|| {
Error::RemoteMetadata("Unable to extract `url` from remote server`".into())
})?
.ok_or_else(|| Error::InvalidResponseType("url", "string", url.clone()))?
.to_string();
#[cfg(target_os = "windows")]
{
with_elevated_task = current_target_data
.get("with_elevated_task")
.and_then(|v| v.as_bool())
.unwrap_or_default();
.map(|v| {
v.as_bool().ok_or_else(|| {
Error::InvalidResponseType("with_elevated_task", "boolean", v.clone())
})
})
.unwrap_or(Ok(false))?;
}
} else {
// make sure we have an available platform from the static
return Err(Error::RemoteMetadata("Platform not available".into()));
return Err(Error::TargetNotFound(target.into()));
}
}
// We don't have the `platforms` field announced, let's assume our
// download URL is at the root of the JSON.
None => {
download_url = release
signature = release
.get("signature")
.ok_or(Error::MissingResponseField("signature"))
.and_then(|signature| {
signature
.as_str()
.ok_or_else(|| Error::InvalidResponseType("signature", "string", signature.clone()))
})?;
let url = release
.get("url")
.ok_or_else(|| Error::RemoteMetadata("Release missing `url`".into()))?
.ok_or(Error::MissingResponseField("url"))?;
download_url = url
.as_str()
.ok_or_else(|| {
Error::RemoteMetadata("Unable to extract `url` from remote server`".into())
})?
.ok_or_else(|| Error::InvalidResponseType("url", "string", url.clone()))?
.to_string();
#[cfg(target_os = "windows")]
{
@@ -171,7 +191,7 @@ impl RemoteRelease {
date,
download_url,
body,
signature,
signature: signature.to_string(),
#[cfg(target_os = "windows")]
with_elevated_task,
})
@@ -356,13 +376,11 @@ impl<'a, R: Runtime> UpdateBuilder<'a, R> {
// Last error is cleaned on success -- shouldn't be triggered if
// we have a successful call
if let Some(error) = last_error {
return Err(Error::Network(error.to_string()));
return Err(error);
}
// Extracted remote metadata
let final_release = remote_release.ok_or_else(|| {
Error::RemoteMetadata("Unable to extract update metadata from the remote server.".into())
})?;
let final_release = remote_release.ok_or(Error::ReleaseNotFound)?;
// did the announced version is greated than our current one?
let should_update =
@@ -412,7 +430,7 @@ pub struct Update<R: Runtime> {
/// Download URL announced
download_url: String,
/// Signature announced
signature: Option<String>,
signature: String,
#[cfg(target_os = "windows")]
/// Optional: Windows only try to use elevated task
/// Default to false
@@ -521,14 +539,7 @@ impl<R: Runtime> Update<R> {
// We need an announced signature by the server
// if there is no signature, bail out.
if let Some(signature) = &self.signature {
// we make sure the archive is valid and signed with the private key linked with the publickey
verify_signature(&mut archive_buffer, signature, &pub_key)?;
} else {
// We have a public key inside our source file, but not announced by the server,
// we assume this update is NOT valid.
return Err(Error::MissingUpdaterSignature);
}
verify_signature(&mut archive_buffer, &self.signature, &pub_key)?;
#[cfg(feature = "updater")]
{
@@ -816,7 +827,9 @@ pub fn extract_path_from_executable(env: &Env, executable_path: &Path) -> PathBu
// Convert base64 to string and prevent failing
fn base64_to_string(base64_string: &str) -> Result<String> {
let decoded_string = &decode(base64_string)?;
let result = from_utf8(decoded_string)?.to_string();
let result = from_utf8(decoded_string)
.map_err(|_| Error::SignatureUtf8(base64_string.into()))?
.to_string();
Ok(result)
}
@@ -868,19 +881,19 @@ mod test {
"platforms": {
"darwin-aarch64": {
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJZVGdpKzJmRWZ0SkRvWS9TdFpqTU9xcm1mUmJSSG5OWVlwSklrWkN1SFpWbmh4SDlBcTU3SXpjbm0xMmRjRkphbkpVeGhGcTdrdzlrWGpGVWZQSWdzPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1MDU3CWZpbGU6L1VzZXJzL3J1bm5lci9ydW5uZXJzLzIuMjYzLjAvd29yay90YXVyaS90YXVyaS90YXVyaS9leGFtcGxlcy9jb21tdW5pY2F0aW9uL3NyYy10YXVyaS90YXJnZXQvZGVidWcvYnVuZGxlL29zeC9hcHAuYXBwLnRhci5negp4ZHFlUkJTVnpGUXdDdEhydTE5TGgvRlVPeVhjTnM5RHdmaGx3c0ZPWjZXWnFwVDRNWEFSbUJTZ1ZkU1IwckJGdmlwSzJPd00zZEZFN2hJOFUvL1FDZz09Cg==",
"url": "https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.app.tar.gz"
"url": "https://github.com/tauri-apps/updater-test/releases/download/v1.0.0/app.app.tar.gz"
},
"darwin-x86_64": {
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJZVGdpKzJmRWZ0SkRvWS9TdFpqTU9xcm1mUmJSSG5OWVlwSklrWkN1SFpWbmh4SDlBcTU3SXpjbm0xMmRjRkphbkpVeGhGcTdrdzlrWGpGVWZQSWdzPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1MDU3CWZpbGU6L1VzZXJzL3J1bm5lci9ydW5uZXJzLzIuMjYzLjAvd29yay90YXVyaS90YXVyaS90YXVyaS9leGFtcGxlcy9jb21tdW5pY2F0aW9uL3NyYy10YXVyaS90YXJnZXQvZGVidWcvYnVuZGxlL29zeC9hcHAuYXBwLnRhci5negp4ZHFlUkJTVnpGUXdDdEhydTE5TGgvRlVPeVhjTnM5RHdmaGx3c0ZPWjZXWnFwVDRNWEFSbUJTZ1ZkU1IwckJGdmlwSzJPd00zZEZFN2hJOFUvL1FDZz09Cg==",
"url": "https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.app.tar.gz"
"url": "https://github.com/tauri-apps/updater-test/releases/download/v1.0.0/app.app.tar.gz"
},
"linux-x86_64": {
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOWZSM29hTFNmUEdXMHRoOC81WDFFVVFRaXdWOUdXUUdwT0NlMldqdXkyaWVieXpoUmdZeXBJaXRqSm1YVmczNXdRL1Brc0tHb1NOTzhrL1hadFcxdmdnPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE3MzQzCWZpbGU6L2hvbWUvcnVubmVyL3dvcmsvdGF1cmkvdGF1cmkvdGF1cmkvZXhhbXBsZXMvY29tbXVuaWNhdGlvbi9zcmMtdGF1cmkvdGFyZ2V0L2RlYnVnL2J1bmRsZS9hcHBpbWFnZS9hcHAuQXBwSW1hZ2UudGFyLmd6CmRUTUM2bWxnbEtTbUhOZGtERUtaZnpUMG5qbVo5TGhtZWE1SFNWMk5OOENaVEZHcnAvVW0zc1A2ajJEbWZUbU0yalRHT0FYYjJNVTVHOHdTQlYwQkF3PT0K",
"url": "https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.AppImage.tar.gz"
"url": "https://github.com/tauri-apps/updater-test/releases/download/v1.0.0/app.AppImage.tar.gz"
},
"windows-x86_64": {
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJHMWlvTzRUSlQzTHJOMm5waWpic0p0VVI2R0hUNGxhQVMxdzBPRndlbGpXQXJJakpTN0toRURtVzBkcm15R0VaNTJuS1lZRWdzMzZsWlNKUVAzZGdJPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1NTIzCWZpbGU6RDpcYVx0YXVyaVx0YXVyaVx0YXVyaVxleGFtcGxlc1xjb21tdW5pY2F0aW9uXHNyYy10YXVyaVx0YXJnZXRcZGVidWdcYXBwLng2NC5tc2kuemlwCitXa1lQc3A2MCs1KzEwZnVhOGxyZ2dGMlZqbjBaVUplWEltYUdyZ255eUF6eVF1dldWZzFObStaVEQ3QU1RS1lzcjhDVU4wWFovQ1p1QjJXbW1YZUJ3PT0K",
"url": "https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.x64.msi.zip"
"url": "https://github.com/tauri-apps/updater-test/releases/download/v1.0.0/app.x64.msi.zip"
}
}
}"#.into()
@@ -926,15 +939,6 @@ mod test {
)
}
fn generate_sample_bad_json() -> String {
r#"{
"version": "v0.0.3",
"notes": "Blablaa",
"date": "2020-02-20T15:41:00Z",
"download_link": "https://github.com/lemarier/tauri-test/releases/download/v0.0.1/update3.tar.gz"
}"#.into()
}
#[test]
fn simple_http_updater() {
let _m = mockito::mock("GET", "/")
@@ -992,10 +996,10 @@ mod test {
assert!(updater.should_update);
assert_eq!(updater.version, "2.0.0");
assert_eq!(updater.signature, Some("dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJHMWlvTzRUSlQzTHJOMm5waWpic0p0VVI2R0hUNGxhQVMxdzBPRndlbGpXQXJJakpTN0toRURtVzBkcm15R0VaNTJuS1lZRWdzMzZsWlNKUVAzZGdJPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1NTIzCWZpbGU6RDpcYVx0YXVyaVx0YXVyaVx0YXVyaVxleGFtcGxlc1xjb21tdW5pY2F0aW9uXHNyYy10YXVyaVx0YXJnZXRcZGVidWdcYXBwLng2NC5tc2kuemlwCitXa1lQc3A2MCs1KzEwZnVhOGxyZ2dGMlZqbjBaVUplWEltYUdyZ255eUF6eVF1dldWZzFObStaVEQ3QU1RS1lzcjhDVU4wWFovQ1p1QjJXbW1YZUJ3PT0K".into()));
assert_eq!(updater.signature, "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJHMWlvTzRUSlQzTHJOMm5waWpic0p0VVI2R0hUNGxhQVMxdzBPRndlbGpXQXJJakpTN0toRURtVzBkcm15R0VaNTJuS1lZRWdzMzZsWlNKUVAzZGdJPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1NTIzCWZpbGU6RDpcYVx0YXVyaVx0YXVyaVx0YXVyaVxleGFtcGxlc1xjb21tdW5pY2F0aW9uXHNyYy10YXVyaVx0YXJnZXRcZGVidWdcYXBwLng2NC5tc2kuemlwCitXa1lQc3A2MCs1KzEwZnVhOGxyZ2dGMlZqbjBaVUplWEltYUdyZ255eUF6eVF1dldWZzFObStaVEQ3QU1RS1lzcjhDVU4wWFovQ1p1QjJXbW1YZUJ3PT0K");
assert_eq!(
updater.download_url,
"https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.x64.msi.zip"
"https://github.com/tauri-apps/updater-test/releases/download/v1.0.0/app.x64.msi.zip"
);
}
@@ -1181,21 +1185,227 @@ mod test {
assert!(updater.should_update);
}
#[test]
fn http_updater_invalid_remote_data() {
let invalid_signature = r#"{
"version": "v0.0.3",
"notes": "Blablaa",
"pub_date": "2020-02-20T15:41:00Z",
"url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz",
"signature": true
}"#;
let invalid_version = r#"{
"version": 5,
"notes": "Blablaa",
"pub_date": "2020-02-20T15:41:00Z",
"url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz",
"signature": "x"
}"#;
let invalid_name = r#"{
"name": false,
"notes": "Blablaa",
"pub_date": "2020-02-20T15:41:00Z",
"url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz",
"signature": "x"
}"#;
let invalid_date = r#"{
"version": "1.0.0",
"notes": "Blablaa",
"pub_date": 345645646,
"url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz",
"signature": "x"
}"#;
let invalid_notes = r#"{
"version": "v0.0.3",
"notes": ["bla", "bla"],
"pub_date": "2020-02-20T15:41:00Z",
"url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz",
"signature": "x"
}"#;
let invalid_url = r#"{
"version": "v0.0.3",
"notes": "Blablaa",
"pub_date": "2020-02-20T15:41:00Z",
"url": ["https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz", "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz"],
"signature": "x"
}"#;
let invalid_platform_signature = r#"{
"version": "v0.0.3",
"notes": "Blablaa",
"pub_date": "2020-02-20T15:41:00Z",
"platforms": {
"test-target": {
"url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz",
"signature": {
"test-target": "x"
}
}
}
}"#;
let invalid_platform_url = r#"{
"version": "v0.0.3",
"notes": "Blablaa",
"pub_date": "2020-02-20T15:41:00Z",
"platforms": {
"test-target": {
"url": {
"first": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz"
}
"signature": "x"
}
}
}"#;
let test_cases = [
(
invalid_signature,
Box::new(|e| matches!(e, Error::InvalidResponseType("signature", "string", _)))
as Box<dyn FnOnce(Error) -> bool>,
),
(
invalid_version,
Box::new(|e| matches!(e, Error::InvalidResponseType("version", "string", _)))
as Box<dyn FnOnce(Error) -> bool>,
),
(
invalid_name,
Box::new(|e| matches!(e, Error::InvalidResponseType("name", "string", _)))
as Box<dyn FnOnce(Error) -> bool>,
),
(
invalid_date,
Box::new(|e| matches!(e, Error::InvalidResponseType("pub_date", "string", _)))
as Box<dyn FnOnce(Error) -> bool>,
),
(
invalid_notes,
Box::new(|e| matches!(e, Error::InvalidResponseType("notes", "string", _)))
as Box<dyn FnOnce(Error) -> bool>,
),
(
invalid_url,
Box::new(|e| matches!(e, Error::InvalidResponseType("url", "string", _)))
as Box<dyn FnOnce(Error) -> bool>,
),
(
invalid_platform_signature,
Box::new(|e| matches!(e, Error::InvalidResponseType("signature", "string", _)))
as Box<dyn FnOnce(Error) -> bool>,
),
(
invalid_platform_url,
Box::new(|e| matches!(e, Error::InvalidResponseType("url", "string", _)))
as Box<dyn FnOnce(Error) -> bool>,
),
];
for (response, validator) in test_cases {
let _m = mockito::mock("GET", "/")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(response)
.create();
let app = crate::test::mock_app();
let check_update = block!(builder(app.handle())
.url(mockito::server_url())
.current_version("0.0.1")
.target("test-target")
.build());
if let Err(e) = check_update {
validator(e);
} else {
panic!("unexpected Ok response");
}
}
}
#[test]
fn http_updater_missing_remote_data() {
let _m = mockito::mock("GET", "/")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(generate_sample_bad_json())
.create();
let missing_signature = r#"{
"version": "v0.0.3",
"notes": "Blablaa",
"pub_date": "2020-02-20T15:41:00Z",
"url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz"
}"#;
let missing_version = r#"{
"notes": "Blablaa",
"pub_date": "2020-02-20T15:41:00Z",
"url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz",
"signature": "x"
}"#;
let missing_url = r#"{
"version": "v0.0.3",
"notes": "Blablaa",
"pub_date": "2020-02-20T15:41:00Z",
"signature": "x"
}"#;
let missing_target = r#"{
"version": "v0.0.3",
"notes": "Blablaa",
"pub_date": "2020-02-20T15:41:00Z",
"platforms": {
"unknown-target": {
"url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz",
"signature": "x"
}
}
}"#;
let missing_platform_signature = r#"{
"version": "v0.0.3",
"notes": "Blablaa",
"pub_date": "2020-02-20T15:41:00Z",
"platforms": {
"test-target": {
"url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz"
}
}
}"#;
let missing_platform_url = r#"{
"version": "v0.0.3",
"notes": "Blablaa",
"pub_date": "2020-02-20T15:41:00Z",
"platforms": {
"test-target": {
"signature": "x"
}
}
}"#;
let app = crate::test::mock_app();
let check_update = block!(builder(app.handle())
.url(mockito::server_url())
.current_version("0.0.1")
.build());
let test_cases = [
(missing_signature, Error::MissingResponseField("signature")),
(
missing_version,
Error::MissingResponseField("version or name"),
),
(missing_url, Error::MissingResponseField("url")),
(missing_target, Error::TargetNotFound("test-target".into())),
(
missing_platform_signature,
Error::MissingResponseField("signature"),
),
(missing_platform_url, Error::MissingResponseField("url")),
];
assert!(check_update.is_err());
for (response, error) in test_cases {
let _m = mockito::mock("GET", "/")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(response)
.create();
let app = crate::test::mock_app();
let check_update = block!(builder(app.handle())
.url(mockito::server_url())
.current_version("0.0.1")
.target("test-target")
.build());
if let Err(e) = check_update {
assert_eq!(e.to_string(), error.to_string());
} else {
panic!("unexpected Ok response");
}
}
}
// run complete process on mac only for now as we don't have

View File

@@ -24,17 +24,17 @@ pub enum Error {
#[error("Signature decoding error: {0}")]
Base64(#[from] base64::DecodeError),
/// UTF8 Errors in signature.
#[error("Signature encoding error: {0}")]
Utf8(#[from] std::str::Utf8Error),
#[error("The signature {0} could not be decoded, please check if it is a valid base64 string. The signature must be the contents of the `.sig` file generated by the Tauri bundler, as a string.")]
SignatureUtf8(String),
/// Tauri utils, mainly extract and file move.
#[error("Tauri API error: {0}")]
TauriApi(#[from] crate::api::Error),
/// Network error.
#[error("Network error: {0}")]
Network(String),
/// Metadata (JSON) error.
#[error("Remote JSON error: {0}")]
RemoteMetadata(String),
/// Could not fetch a valid response from the server.
#[error("Could not fetch a valid release JSON from the remote")]
ReleaseNotFound,
/// Error building updater.
#[error("Unable to prepare the updater: {0}")]
Builder(String),
@@ -44,13 +44,19 @@ pub enum Error {
/// Updater is not supported for current operating system or platform.
#[error("Unsupported operating system or platform")]
UnsupportedPlatform,
/// Public key found in `tauri.conf.json` but no signature announced remotely.
#[error("Signature not available, skipping update")]
MissingUpdaterSignature,
/// The platform was not found on the updater JSON response.
#[error("the platform `{0}` was not found on the response `platforms` object")]
TargetNotFound(String),
/// Triggered when there is NO error and the two versions are equals.
/// On client side, it's important to catch this error.
#[error("No updates available")]
UpToDate,
/// The server did not include the signature field.
#[error("the `{0}` field was not set on the updater response")]
MissingResponseField(&'static str),
/// The updater responded with an invalid signature type.
#[error("the updater response field `{0}` type is invalid, expected {1} but found {2}")]
InvalidResponseType(&'static str, &'static str, serde_json::Value),
}
pub type Result<T = ()> = std::result::Result<T, Error>;