serializable structured errors
This commit is contained in:
parent
cb26201506
commit
e0c4c849dc
@ -1,3 +1,7 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::convert::AsRef;
|
||||||
|
use strum_macros::AsRefStr;
|
||||||
|
|
||||||
use thiserror::Error as ThisError;
|
use thiserror::Error as ThisError;
|
||||||
|
|
||||||
use aws_sdk_sts::{
|
use aws_sdk_sts::{
|
||||||
@ -10,35 +14,70 @@ use sqlx::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use serde::{Serialize, Serializer, ser::SerializeMap};
|
use serde::{Serialize, Serializer, ser::SerializeMap};
|
||||||
use strum_macros::IntoStaticStr;
|
|
||||||
|
|
||||||
|
|
||||||
pub struct SerializeError<E> {
|
// pub struct SerializeError<E> {
|
||||||
pub err: E
|
// pub err: E,
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl<E: std::error::Error> Serialize for SerializeError<E>
|
// impl<E: std::error::Error> Serialize for SerializeError<E>
|
||||||
|
// {
|
||||||
|
// fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
// let mut map = serializer.serialize_map(None)?;
|
||||||
|
// map.serialize_entry("msg", &format!("{}", self.err))?;
|
||||||
|
// if let Some(src) = self.err.source() {
|
||||||
|
// let ser_src = SerializeError { err: src };
|
||||||
|
// map.serialize_entry("source", &ser_src)?;
|
||||||
|
// }
|
||||||
|
// map.end()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl<E: std::error::Error> From<E> for SerializeError<E> {
|
||||||
|
// fn from(err: E) -> Self {
|
||||||
|
// SerializeError { err }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
fn serialize_basic_err<E, S>(err: &E, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
E: std::error::Error + AsRef<str>,
|
||||||
|
S: Serializer,
|
||||||
{
|
{
|
||||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
||||||
let mut map = serializer.serialize_map(None)?;
|
let mut map = serializer.serialize_map(None)?;
|
||||||
map.serialize_entry("msg", &format!("{}", self.err))?;
|
map.serialize_entry("code", err.as_ref())?;
|
||||||
if let Some(src) = self.err.source() {
|
map.serialize_entry("msg", &format!("{err}"))?;
|
||||||
let ser_src = SerializeError { err: src };
|
if let Some(src) = err.source() {
|
||||||
map.serialize_entry("source", &ser_src)?;
|
map.serialize_entry("source", &format!("{src}"))?;
|
||||||
}
|
}
|
||||||
map.end()
|
map.end()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: std::error::Error> From<E> for SerializeError<E> {
|
|
||||||
fn from(err: E) -> Self {
|
fn serialize_upstream_err<E, M>(err: &E, map: &mut M) -> Result<(), M::Error>
|
||||||
SerializeError { err }
|
where
|
||||||
|
E: Error,
|
||||||
|
M: serde::ser::SerializeMap,
|
||||||
|
{
|
||||||
|
let src = err.source().map(|s| format!("{s}"));
|
||||||
|
map.serialize_entry("source", &src)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
macro_rules! impl_serialize_basic {
|
||||||
|
($err_type:ident) => {
|
||||||
|
impl Serialize for $err_type {
|
||||||
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
serialize_basic_err(self, serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// error during initial setup (primarily loading state from db)
|
// error during initial setup (primarily loading state from db)
|
||||||
#[derive(Debug, ThisError)]
|
#[derive(Debug, ThisError, AsRefStr)]
|
||||||
pub enum SetupError {
|
pub enum SetupError {
|
||||||
#[error("Invalid database record")]
|
#[error("Invalid database record")]
|
||||||
InvalidRecord, // e.g. wrong size blob for nonce or salt
|
InvalidRecord, // e.g. wrong size blob for nonce or salt
|
||||||
@ -52,7 +91,7 @@ pub enum SetupError {
|
|||||||
|
|
||||||
|
|
||||||
// error when attempting to tell a request handler whether to release or deny credentials
|
// error when attempting to tell a request handler whether to release or deny credentials
|
||||||
#[derive(Debug, ThisError, IntoStaticStr)]
|
#[derive(Debug, ThisError, AsRefStr)]
|
||||||
pub enum SendResponseError {
|
pub enum SendResponseError {
|
||||||
#[error("The specified credentials request was not found")]
|
#[error("The specified credentials request was not found")]
|
||||||
NotFound, // no request with the given id
|
NotFound, // no request with the given id
|
||||||
@ -62,7 +101,7 @@ pub enum SendResponseError {
|
|||||||
|
|
||||||
|
|
||||||
// errors encountered while handling an HTTP request
|
// errors encountered while handling an HTTP request
|
||||||
#[derive(Debug, ThisError)]
|
#[derive(Debug, ThisError, AsRefStr)]
|
||||||
pub enum RequestError {
|
pub enum RequestError {
|
||||||
#[error("Error writing to stream: {0}")]
|
#[error("Error writing to stream: {0}")]
|
||||||
StreamIOError(#[from] std::io::Error),
|
StreamIOError(#[from] std::io::Error),
|
||||||
@ -82,7 +121,7 @@ pub enum RequestError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, ThisError)]
|
#[derive(Debug, ThisError, AsRefStr)]
|
||||||
pub enum GetCredentialsError {
|
pub enum GetCredentialsError {
|
||||||
#[error("Credentials are currently locked")]
|
#[error("Credentials are currently locked")]
|
||||||
Locked,
|
Locked,
|
||||||
@ -91,7 +130,7 @@ pub enum GetCredentialsError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, ThisError)]
|
#[derive(Debug, ThisError, AsRefStr)]
|
||||||
pub enum GetSessionError {
|
pub enum GetSessionError {
|
||||||
#[error("Request completed successfully but no credentials were returned")]
|
#[error("Request completed successfully but no credentials were returned")]
|
||||||
NoCredentials, // SDK returned successfully but credentials are None
|
NoCredentials, // SDK returned successfully but credentials are None
|
||||||
@ -100,7 +139,7 @@ pub enum GetSessionError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, ThisError)]
|
#[derive(Debug, ThisError, AsRefStr)]
|
||||||
pub enum UnlockError {
|
pub enum UnlockError {
|
||||||
#[error("App is not locked")]
|
#[error("App is not locked")]
|
||||||
NotLocked,
|
NotLocked,
|
||||||
@ -118,10 +157,87 @@ pub enum UnlockError {
|
|||||||
|
|
||||||
|
|
||||||
// Errors encountered while trying to figure out who's on the other end of a request
|
// Errors encountered while trying to figure out who's on the other end of a request
|
||||||
#[derive(Debug, ThisError)]
|
#[derive(Debug, ThisError, AsRefStr)]
|
||||||
pub enum ClientInfoError {
|
pub enum ClientInfoError {
|
||||||
#[error("Found PID for client socket, but no corresponding process")]
|
#[error("Found PID for client socket, but no corresponding process")]
|
||||||
ProcessNotFound,
|
ProcessNotFound,
|
||||||
#[error("Couldn't get client socket details: {0}")]
|
#[error("Couldn't get client socket details: {0}")]
|
||||||
NetstatError(#[from] netstat2::error::Error),
|
NetstatError(#[from] netstat2::error::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// Serialize implementations
|
||||||
|
// =========================
|
||||||
|
|
||||||
|
|
||||||
|
struct SerializeWrapper<E>(pub E);
|
||||||
|
|
||||||
|
impl Serialize for SerializeWrapper<&GetSessionTokenError> {
|
||||||
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
let err = self.0;
|
||||||
|
let mut map = serializer.serialize_map(None)?;
|
||||||
|
map.serialize_entry("code", &err.code())?;
|
||||||
|
map.serialize_entry("msg", &err.message())?;
|
||||||
|
map.serialize_entry("source", &None::<&str>)?;
|
||||||
|
map.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
impl_serialize_basic!(SetupError);
|
||||||
|
impl_serialize_basic!(SendResponseError);
|
||||||
|
impl_serialize_basic!(GetCredentialsError);
|
||||||
|
impl_serialize_basic!(ClientInfoError);
|
||||||
|
|
||||||
|
|
||||||
|
impl Serialize for RequestError {
|
||||||
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
let mut map = serializer.serialize_map(None)?;
|
||||||
|
map.serialize_entry("code", self.as_ref())?;
|
||||||
|
map.serialize_entry("msg", &format!("{self}"))?;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
RequestError::NoCredentials(src) => map.serialize_entry("source", &src)?,
|
||||||
|
RequestError::ClientInfo(src) => map.serialize_entry("source", &src)?,
|
||||||
|
_ => serialize_upstream_err(self, &mut map)?,
|
||||||
|
}
|
||||||
|
|
||||||
|
map.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Serialize for GetSessionError {
|
||||||
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
let mut map = serializer.serialize_map(None)?;
|
||||||
|
map.serialize_entry("code", self.as_ref())?;
|
||||||
|
map.serialize_entry("msg", &format!("{self}"))?;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
GetSessionError::SdkError(AwsSdkError::ServiceError(se_wrapper)) => {
|
||||||
|
let err = se_wrapper.err();
|
||||||
|
map.serialize_entry("source", &SerializeWrapper(err))?
|
||||||
|
}
|
||||||
|
_ => serialize_upstream_err(self, &mut map)?,
|
||||||
|
}
|
||||||
|
|
||||||
|
map.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Serialize for UnlockError {
|
||||||
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
let mut map = serializer.serialize_map(None)?;
|
||||||
|
map.serialize_entry("code", self.as_ref())?;
|
||||||
|
map.serialize_entry("msg", &format!("{self}"))?;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
UnlockError::GetSession(src) => map.serialize_entry("source", &src)?,
|
||||||
|
_ => serialize_upstream_err(self, &mut map)?,
|
||||||
|
}
|
||||||
|
map.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -36,10 +36,8 @@ pub fn respond(response: RequestResponse, app_state: State<'_, AppState>) -> Res
|
|||||||
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn unlock(passphrase: String, app_state: State<'_, AppState>) -> Result<(), SerializeError<UnlockError>> {
|
pub async fn unlock(passphrase: String, app_state: State<'_, AppState>) -> Result<(), UnlockError> {
|
||||||
app_state.decrypt(&passphrase)
|
app_state.decrypt(&passphrase).await
|
||||||
.await
|
|
||||||
.map_err(|e| SerializeError::from(e))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -59,10 +57,8 @@ pub async fn save_credentials(
|
|||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
passphrase: String,
|
passphrase: String,
|
||||||
app_state: State<'_, AppState>
|
app_state: State<'_, AppState>
|
||||||
) -> Result<(), SerializeError<UnlockError>> {
|
) -> Result<(), UnlockError> {
|
||||||
app_state.save_creds(credentials, &passphrase)
|
app_state.save_creds(credentials, &passphrase).await
|
||||||
.await
|
|
||||||
.map_err(|e| SerializeError::from(e))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount, createEventDispatcher } from 'svelte';
|
import { onMount, createEventDispatcher } from 'svelte';
|
||||||
import { invoke } from '@tauri-apps/api/tauri';
|
import { invoke } from '@tauri-apps/api/tauri';
|
||||||
|
import { getRootCause } from '../lib/errors.js';
|
||||||
|
|
||||||
export let appState;
|
export let appState;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
let error = null;
|
let errorMsg = null;
|
||||||
let AccessKeyId, SecretAccessKey, passphrase
|
let AccessKeyId, SecretAccessKey, passphrase
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
try {
|
|
||||||
console.log('Saving credentials.');
|
console.log('Saving credentials.');
|
||||||
let credentials = {AccessKeyId, SecretAccessKey};
|
let credentials = {AccessKeyId, SecretAccessKey};
|
||||||
|
|
||||||
@ -23,19 +23,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
error = e;
|
|
||||||
window.error = e;
|
window.error = e;
|
||||||
|
if (e.code === "GetSession") {
|
||||||
|
let root = getRootCause(e);
|
||||||
|
errorMsg = `Error response from AWS (${root.code}): ${root.msg}`;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
errorMsg = e.msg;
|
||||||
}
|
}
|
||||||
catch (e) {
|
|
||||||
console.log("Error saving credentials:", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if error}
|
{#if errorMsg}
|
||||||
<div class="text-red-400">{error}</div>
|
<div class="text-red-400">{errorMsg}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<form action="#" on:submit|preventDefault="{save}">
|
<form action="#" on:submit|preventDefault="{save}">
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
<script>
|
<script>
|
||||||
import { invoke } from '@tauri-apps/api/tauri';
|
import { invoke } from '@tauri-apps/api/tauri';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import { getRootCause } from '../lib/errors.js';
|
||||||
import Button from '../ui/Button.svelte';
|
import Button from '../ui/Button.svelte';
|
||||||
|
|
||||||
export let appState;
|
export let appState;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
let error = null;
|
let errorMsg = null;
|
||||||
let passphrase = '';
|
let passphrase = '';
|
||||||
async function unlock() {
|
async function unlock() {
|
||||||
console.log('invoking unlock command.')
|
console.log('invoking unlock command.')
|
||||||
@ -23,14 +24,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
error = e;
|
|
||||||
window.error = e;
|
window.error = e;
|
||||||
|
if (e.code === 'GetSession') {
|
||||||
|
let root = getRootCause(e);
|
||||||
|
errorMsg = `Error response from AWS (${root.code}): ${root.msg}`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
errorMsg = e.msg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if error}
|
{#if errorMsg}
|
||||||
<div class="text-red-400">{error}</div>
|
<div class="text-red-400">{errorMsg}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<form action="#" on:submit|preventDefault="{unlock}">
|
<form action="#" on:submit|preventDefault="{unlock}">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user