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 aws_sdk_sts::{
|
||||
@ -10,35 +14,70 @@ use sqlx::{
|
||||
};
|
||||
|
||||
use serde::{Serialize, Serializer, ser::SerializeMap};
|
||||
use strum_macros::IntoStaticStr;
|
||||
|
||||
|
||||
pub struct SerializeError<E> {
|
||||
pub err: E
|
||||
}
|
||||
// pub struct SerializeError<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)?;
|
||||
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()
|
||||
let mut map = serializer.serialize_map(None)?;
|
||||
map.serialize_entry("code", err.as_ref())?;
|
||||
map.serialize_entry("msg", &format!("{err}"))?;
|
||||
if let Some(src) = err.source() {
|
||||
map.serialize_entry("source", &format!("{src}"))?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
|
||||
impl<E: std::error::Error> From<E> for SerializeError<E> {
|
||||
fn from(err: E) -> Self {
|
||||
SerializeError { err }
|
||||
|
||||
fn serialize_upstream_err<E, M>(err: &E, map: &mut M) -> Result<(), M::Error>
|
||||
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)
|
||||
#[derive(Debug, ThisError)]
|
||||
#[derive(Debug, ThisError, AsRefStr)]
|
||||
pub enum SetupError {
|
||||
#[error("Invalid database record")]
|
||||
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
|
||||
#[derive(Debug, ThisError, IntoStaticStr)]
|
||||
#[derive(Debug, ThisError, AsRefStr)]
|
||||
pub enum SendResponseError {
|
||||
#[error("The specified credentials request was not found")]
|
||||
NotFound, // no request with the given id
|
||||
@ -62,7 +101,7 @@ pub enum SendResponseError {
|
||||
|
||||
|
||||
// errors encountered while handling an HTTP request
|
||||
#[derive(Debug, ThisError)]
|
||||
#[derive(Debug, ThisError, AsRefStr)]
|
||||
pub enum RequestError {
|
||||
#[error("Error writing to stream: {0}")]
|
||||
StreamIOError(#[from] std::io::Error),
|
||||
@ -82,7 +121,7 @@ pub enum RequestError {
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, ThisError)]
|
||||
#[derive(Debug, ThisError, AsRefStr)]
|
||||
pub enum GetCredentialsError {
|
||||
#[error("Credentials are currently locked")]
|
||||
Locked,
|
||||
@ -91,7 +130,7 @@ pub enum GetCredentialsError {
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, ThisError)]
|
||||
#[derive(Debug, ThisError, AsRefStr)]
|
||||
pub enum GetSessionError {
|
||||
#[error("Request completed successfully but no credentials were returned")]
|
||||
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 {
|
||||
#[error("App is not locked")]
|
||||
NotLocked,
|
||||
@ -118,10 +157,87 @@ pub enum UnlockError {
|
||||
|
||||
|
||||
// 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 {
|
||||
#[error("Found PID for client socket, but no corresponding process")]
|
||||
ProcessNotFound,
|
||||
#[error("Couldn't get client socket details: {0}")]
|
||||
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]
|
||||
pub async fn unlock(passphrase: String, app_state: State<'_, AppState>) -> Result<(), SerializeError<UnlockError>> {
|
||||
app_state.decrypt(&passphrase)
|
||||
.await
|
||||
.map_err(|e| SerializeError::from(e))
|
||||
pub async fn unlock(passphrase: String, app_state: State<'_, AppState>) -> Result<(), UnlockError> {
|
||||
app_state.decrypt(&passphrase).await
|
||||
}
|
||||
|
||||
|
||||
@ -59,10 +57,8 @@ pub async fn save_credentials(
|
||||
credentials: Credentials,
|
||||
passphrase: String,
|
||||
app_state: State<'_, AppState>
|
||||
) -> Result<(), SerializeError<UnlockError>> {
|
||||
app_state.save_creds(credentials, &passphrase)
|
||||
.await
|
||||
.map_err(|e| SerializeError::from(e))
|
||||
) -> Result<(), UnlockError> {
|
||||
app_state.save_creds(credentials, &passphrase).await
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,41 +1,42 @@
|
||||
<script>
|
||||
import { onMount, createEventDispatcher } from 'svelte';
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { getRootCause } from '../lib/errors.js';
|
||||
|
||||
export let appState;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let error = null;
|
||||
let errorMsg = null;
|
||||
let AccessKeyId, SecretAccessKey, passphrase
|
||||
|
||||
async function save() {
|
||||
console.log('Saving credentials.');
|
||||
let credentials = {AccessKeyId, SecretAccessKey};
|
||||
|
||||
try {
|
||||
console.log('Saving credentials.');
|
||||
let credentials = {AccessKeyId, SecretAccessKey};
|
||||
|
||||
try {
|
||||
await invoke('save_credentials', {credentials, passphrase});
|
||||
if (appState.currentRequest) {
|
||||
dispatch('navigate', {target: 'ShowApproved'})
|
||||
}
|
||||
else {
|
||||
dispatch('navigate', {target: 'Home'})
|
||||
}
|
||||
await invoke('save_credentials', {credentials, passphrase});
|
||||
if (appState.currentRequest) {
|
||||
dispatch('navigate', {target: 'ShowApproved'})
|
||||
}
|
||||
catch (e) {
|
||||
error = e;
|
||||
window.error = e;
|
||||
else {
|
||||
dispatch('navigate', {target: 'Home'})
|
||||
}
|
||||
|
||||
}
|
||||
catch (e) {
|
||||
console.log("Error saving credentials:", 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>
|
||||
|
||||
{#if error}
|
||||
<div class="text-red-400">{error}</div>
|
||||
{#if errorMsg}
|
||||
<div class="text-red-400">{errorMsg}</div>
|
||||
{/if}
|
||||
|
||||
<form action="#" on:submit|preventDefault="{save}">
|
||||
|
@ -1,13 +1,14 @@
|
||||
<script>
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { getRootCause } from '../lib/errors.js';
|
||||
import Button from '../ui/Button.svelte';
|
||||
|
||||
export let appState;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let error = null;
|
||||
let errorMsg = null;
|
||||
let passphrase = '';
|
||||
async function unlock() {
|
||||
console.log('invoking unlock command.')
|
||||
@ -23,14 +24,20 @@
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
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>
|
||||
|
||||
{#if error}
|
||||
<div class="text-red-400">{error}</div>
|
||||
{#if errorMsg}
|
||||
<div class="text-red-400">{errorMsg}</div>
|
||||
{/if}
|
||||
|
||||
<form action="#" on:submit|preventDefault="{unlock}">
|
||||
|
Loading…
x
Reference in New Issue
Block a user