+ export let errorStrings: string[];
+
+ $: uniqueErrors = [...new Set(errorStrings)];
+
+
+{#if uniqueErrors.length === 1}
+ {uniqueErrors[0]}
+{:else}
+
+ {#each uniqueErrors as error (error)}
+ - {error}
+ {/each}
+
+{/if}
+
+
diff --git a/mathesar_ui/src/components/errors/Errors.svelte b/mathesar_ui/src/components/errors/Errors.svelte
new file mode 100644
index 0000000000..bd953dfeb4
--- /dev/null
+++ b/mathesar_ui/src/components/errors/Errors.svelte
@@ -0,0 +1,37 @@
+
+
+
+ {#each richErrors as richError}
+
+
+
+ {/each}
+
+ {#if stringErrors.length}
+
+
+
+ {/if}
+
+
+
diff --git a/mathesar_ui/src/components/errors/customized/NoConnection.svelte b/mathesar_ui/src/components/errors/customized/NoConnection.svelte
new file mode 100644
index 0000000000..46fee60a5a
--- /dev/null
+++ b/mathesar_ui/src/components/errors/customized/NoConnection.svelte
@@ -0,0 +1,8 @@
+
+
+{$_('not_a_collaborator_help')}
+
diff --git a/mathesar_ui/src/components/errors/customized/UnableToConnect.svelte b/mathesar_ui/src/components/errors/customized/UnableToConnect.svelte
new file mode 100644
index 0000000000..e25ea0fa51
--- /dev/null
+++ b/mathesar_ui/src/components/errors/customized/UnableToConnect.svelte
@@ -0,0 +1,8 @@
+
+
+{$_('unable_to_connect_to_database')}
+{message}
diff --git a/mathesar_ui/src/components/errors/errorUtils.ts b/mathesar_ui/src/components/errors/errorUtils.ts
new file mode 100644
index 0000000000..6415316be3
--- /dev/null
+++ b/mathesar_ui/src/components/errors/errorUtils.ts
@@ -0,0 +1,74 @@
+import { distinct } from 'iter-tools';
+import type { ComponentProps, ComponentType, SvelteComponent } from 'svelte';
+
+import type { ComponentWithProps } from '@mathesar/component-library/types';
+import type { RpcError } from '@mathesar/packages/json-rpc-client-builder';
+
+import NoConnection from './customized/NoConnection.svelte';
+import UnableToConnect from './customized/UnableToConnect.svelte';
+
+const NO_CONNECTION_AVAILABLE = -28030;
+const PSYCOPG_OPERATIONAL_ERROR = -30193;
+
+export type GeneralizedError = string | RpcError;
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+type ReturnableComponent = ComponentWithProps;
+
+function component(
+ c: ComponentType,
+ p: ComponentProps,
+): ComponentWithProps {
+ return { component: c, props: p };
+}
+
+function getCustomizedRpcError({
+ code,
+ message,
+}: RpcError): string | ReturnableComponent {
+ switch (code) {
+ case NO_CONNECTION_AVAILABLE:
+ return component(NoConnection, {});
+ case PSYCOPG_OPERATIONAL_ERROR:
+ return component(UnableToConnect, { message });
+ default:
+ return message;
+ }
+}
+
+function getCustomizedError(
+ error: GeneralizedError,
+): string | ReturnableComponent {
+ return typeof error === 'string' ? error : getCustomizedRpcError(error);
+}
+
+function getErrorHash(error: GeneralizedError): string {
+ if (typeof error === 'string') {
+ return JSON.stringify({ type: 'string', error });
+ }
+ const { code, message } = error;
+ return JSON.stringify({ type: 'RpcError', code, message });
+}
+
+export function getDistinctErrors(
+ errors: Iterable,
+): Iterable {
+ return distinct(getErrorHash, errors);
+}
+
+export function groupErrors(errors: Iterable): {
+ stringErrors: string[];
+ richErrors: ReturnableComponent[];
+} {
+ const stringErrors: string[] = [];
+ const richErrors: ReturnableComponent[] = [];
+ for (const error of errors) {
+ const customizedError = getCustomizedError(error);
+ if (typeof customizedError === 'string') {
+ stringErrors.push(customizedError);
+ } else {
+ richErrors.push(customizedError);
+ }
+ }
+ return { stringErrors, richErrors };
+}
diff --git a/mathesar_ui/src/components/form/FieldErrors.svelte b/mathesar_ui/src/components/form/FieldErrors.svelte
index 4c6df052e1..5397b9f6ef 100644
--- a/mathesar_ui/src/components/form/FieldErrors.svelte
+++ b/mathesar_ui/src/components/form/FieldErrors.svelte
@@ -1,5 +1,5 @@