diff --git a/src/dataconnect/errors.ts b/src/dataconnect/errors.ts index 5f6f9666580..f14a9b8c5b5 100644 --- a/src/dataconnect/errors.ts +++ b/src/dataconnect/errors.ts @@ -5,28 +5,27 @@ const PRECONDITION_ERROR_TYPESTRING = "type.googleapis.com/google.rpc.Preconditi const INCOMPATIBLE_CONNECTOR_TYPE = "INCOMPATIBLE_CONNECTOR"; export function getIncompatibleSchemaError(err: any): IncompatibleSqlSchemaError | undefined { - const original = err.context?.body?.error || err.orignal; - if (!original) { - // If we can't get the original, rethrow so we don't cover up the original error. - throw err; + const incompatibles = errorDetails(err, INCOMPATIBLE_SCHEMA_ERROR_TYPESTRING); + if (incompatibles.length === 0) { + return undefined; } - const details: any[] = original.details; - const incompatibles = details.filter((d) => - d["@type"]?.includes(INCOMPATIBLE_SCHEMA_ERROR_TYPESTRING), - ); // Should never get multiple incompatible schema errors - return incompatibles[0]; + const incompatible = incompatibles[0]; + // Extract the violation type from the precondition error detail. + const preconditionErrs = errorDetails(err, PRECONDITION_ERROR_TYPESTRING); + const violationTypes = (incompatible.violationType = preconditionErrs + .flatMap((preCondErr) => preCondErr.violations) + .flatMap((viol) => viol.type) + .filter((type) => type === "INACCESSIBLE_SCHEMA" || type === "INCOMPATIBLE_SCHEMA")); + incompatible.violationType = violationTypes[0]; + return incompatible; } // Note - the backend just includes file name, not the name of the connector resource in the GQLerror extensions. // so we don't use this yet. Ideally, we'd just include connector name in the extensions. export function getInvalidConnectors(err: any): string[] { + const preconditionErrs = errorDetails(err, PRECONDITION_ERROR_TYPESTRING); const invalidConns: string[] = []; - const original = err.context?.body?.error || err?.orignal; - const details: any[] = original?.details; - const preconditionErrs = details?.filter((d) => - d["@type"]?.includes(PRECONDITION_ERROR_TYPESTRING), - ); for (const preconditionErr of preconditionErrs) { const incompatibleConnViolation = preconditionErr?.violations?.filter( (v: { type: string }) => v.type === INCOMPATIBLE_CONNECTOR_TYPE, @@ -36,3 +35,9 @@ export function getInvalidConnectors(err: any): string[] { } return invalidConns; } + +function errorDetails(err: any, ofType: string): any[] { + const original = err.context?.body?.error || err?.original; + const details: any[] = original?.details; + return details?.filter((d) => d["@type"]?.includes(ofType)) || []; +} diff --git a/src/dataconnect/schemaMigration.ts b/src/dataconnect/schemaMigration.ts index a03cbdaa96c..cf49c33a092 100644 --- a/src/dataconnect/schemaMigration.ts +++ b/src/dataconnect/schemaMigration.ts @@ -85,7 +85,7 @@ export async function migrateSchema(args: { if (!shouldDeleteInvalidConnectors && invalidConnectors.length) { const cmd = suggestedCommand(serviceName, invalidConnectors); throw new FirebaseError( - `Command aborted. Try deploying compatible connectors first with ${clc.bold(cmd)}`, + `Command aborted. Try deploying those connectors first with ${clc.bold(cmd)}`, ); } const migrationMode = incompatible @@ -356,11 +356,31 @@ async function ensureServiceIsConnectedToCloudSql( } function displaySchemaChanges(error: IncompatibleSqlSchemaError) { - const message = - "Your new schema is incompatible with the schema of your CloudSQL database. " + - "The following SQL statements will migrate your database schema to match your new Data Connect schema.\n" + - error.diffs.map(toString).join("\n"); - logLabeledWarning("dataconnect", message); + switch (error.violationType) { + case "INCOMPATIBLE_SCHEMA": + { + const message = + "Your new schema is incompatible with the schema of your CloudSQL database. " + + "The following SQL statements will migrate your database schema to match your new Data Connect schema.\n" + + error.diffs.map(toString).join("\n"); + logLabeledWarning("dataconnect", message); + } + break; + case "INACCESSIBLE_SCHEMA": + { + const message = + "Cannot access your CloudSQL database to validate schema. " + + "The following SQL statements can setup a new database schema.\n" + + error.diffs.map(toString).join("\n"); + logLabeledWarning("dataconnect", message); + logLabeledWarning("dataconnect", "Some SQL resources may already exist."); + } + break; + default: + throw new FirebaseError( + `Unknown schema violation type: ${error.violationType}, IncompatibleSqlSchemaError: ${error}`, + ); + } } function toString(diff: Diff) { diff --git a/src/dataconnect/types.ts b/src/dataconnect/types.ts index 7efaee5405f..c2d776fcc58 100644 --- a/src/dataconnect/types.ts +++ b/src/dataconnect/types.ts @@ -48,12 +48,15 @@ export interface File { content: string; } -// An error indicating that the SQL database schema is incomptible with a data connect schema. +// An error indicating that the SQL database schema is incompatible with a data connect schema. export interface IncompatibleSqlSchemaError { - // A list of differences between the two schema with instrucitons how to resolve them. + // A list of differences between the two schema with instructions how to resolve them. diffs: Diff[]; // Whether any of the changes included are destructive. destructive: boolean; + + // The failed precondition validation type. + violationType: "INCOMPATIBLE_SCHEMA" | "INACCESSIBLE_SCHEMA" | string; } export interface Diff {