diff --git a/src/main/java/io/cdap/plugin/gcp/common/GCPErrorDetailsProvider.java b/src/main/java/io/cdap/plugin/gcp/common/GCPErrorDetailsProvider.java index 453854fdc..983561f9f 100644 --- a/src/main/java/io/cdap/plugin/gcp/common/GCPErrorDetailsProvider.java +++ b/src/main/java/io/cdap/plugin/gcp/common/GCPErrorDetailsProvider.java @@ -18,6 +18,7 @@ import com.google.api.client.googleapis.json.GoogleJsonResponseException; import com.google.api.client.http.HttpResponseException; +import com.google.common.base.Strings; import com.google.common.base.Throwables; import io.cdap.cdap.api.exception.ErrorCategory; import io.cdap.cdap.api.exception.ErrorCategory.ErrorCategoryEnum; @@ -75,10 +76,29 @@ private ProgramFailureException getProgramFailureException(HttpResponseException GoogleJsonResponseException exception = (GoogleJsonResponseException) e; errorMessage = exception.getDetails() != null ? exception.getDetails().getMessage() : exception.getMessage(); + + String externalDocumentationLink = getExternalDocumentationLink(); + if (!Strings.isNullOrEmpty(externalDocumentationLink)) { + + if (!errorReason.endsWith(".")) { + errorReason = errorReason + "."; + } + errorReason = String.format("%s For more details, see %s", errorReason, + externalDocumentationLink); + } } return ErrorUtils.getProgramFailureException(new ErrorCategory(ErrorCategoryEnum.PLUGIN), errorReason, String.format(errorMessageFormat, errorContext.getPhase(), errorMessage), pair.getErrorType(), true, e); } + + /** + * Get the external documentation link for the client errors if available. + * + * @return The external documentation link as a {@link String}. + */ + protected String getExternalDocumentationLink() { + return null; + } } diff --git a/src/main/java/io/cdap/plugin/gcp/gcs/GCSErrorDetailsProvider.java b/src/main/java/io/cdap/plugin/gcp/gcs/GCSErrorDetailsProvider.java new file mode 100644 index 000000000..448b81ebe --- /dev/null +++ b/src/main/java/io/cdap/plugin/gcp/gcs/GCSErrorDetailsProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright © 2024 Cask Data, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package io.cdap.plugin.gcp.gcs; + +import io.cdap.plugin.gcp.common.GCPErrorDetailsProvider; + +/** + * A custom ErrorDetailsProvider for GCS plugins. + */ +public class GCSErrorDetailsProvider extends GCPErrorDetailsProvider { + + @Override + protected String getExternalDocumentationLink() { + return "https://cloud.google.com/storage/docs/json_api/v1/status-codes"; + } +} diff --git a/src/main/java/io/cdap/plugin/gcp/gcs/sink/GCSBatchSink.java b/src/main/java/io/cdap/plugin/gcp/gcs/sink/GCSBatchSink.java index 13fc33570..6789676e3 100644 --- a/src/main/java/io/cdap/plugin/gcp/gcs/sink/GCSBatchSink.java +++ b/src/main/java/io/cdap/plugin/gcp/gcs/sink/GCSBatchSink.java @@ -52,9 +52,9 @@ import io.cdap.plugin.format.plugin.FileSinkProperties; import io.cdap.plugin.gcp.common.CmekUtils; import io.cdap.plugin.gcp.common.GCPConnectorConfig; -import io.cdap.plugin.gcp.common.GCPErrorDetailsProvider; import io.cdap.plugin.gcp.common.GCPUtils; import io.cdap.plugin.gcp.gcs.Formats; +import io.cdap.plugin.gcp.gcs.GCSErrorDetailsProvider; import io.cdap.plugin.gcp.gcs.GCSPath; import io.cdap.plugin.gcp.gcs.StorageClient; import io.cdap.plugin.gcp.gcs.connector.GCSConnector; @@ -166,7 +166,7 @@ public void prepareRun(BatchSinkContext context) throws Exception { // set error details provider context.setErrorDetailsProvider( - new ErrorDetailsProviderSpec(GCPErrorDetailsProvider.class.getName())); + new ErrorDetailsProviderSpec(GCSErrorDetailsProvider.class.getName())); // super is called down here to avoid instantiating the lineage recorder with a null asset super.prepareRun(context); diff --git a/src/main/java/io/cdap/plugin/gcp/gcs/sink/GCSMultiBatchSink.java b/src/main/java/io/cdap/plugin/gcp/gcs/sink/GCSMultiBatchSink.java index 202663e63..33310421e 100644 --- a/src/main/java/io/cdap/plugin/gcp/gcs/sink/GCSMultiBatchSink.java +++ b/src/main/java/io/cdap/plugin/gcp/gcs/sink/GCSMultiBatchSink.java @@ -46,8 +46,8 @@ import io.cdap.plugin.common.batch.sink.SinkOutputFormatProvider; import io.cdap.plugin.format.FileFormat; import io.cdap.plugin.gcp.common.CmekUtils; -import io.cdap.plugin.gcp.common.GCPErrorDetailsProvider; import io.cdap.plugin.gcp.common.GCPUtils; +import io.cdap.plugin.gcp.gcs.GCSErrorDetailsProvider; import io.cdap.plugin.gcp.gcs.connector.GCSConnector; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; @@ -173,7 +173,7 @@ public void prepareRun(BatchSinkContext context) throws IOException, Instantiati // set error details provider context.setErrorDetailsProvider( - new ErrorDetailsProviderSpec(GCPErrorDetailsProvider.class.getName())); + new ErrorDetailsProviderSpec(GCSErrorDetailsProvider.class.getName())); Map baseProperties = GCPUtils.getFileSystemProperties(config.connection, config.getPath(), new HashMap<>()); diff --git a/src/main/java/io/cdap/plugin/gcp/gcs/source/GCSSource.java b/src/main/java/io/cdap/plugin/gcp/gcs/source/GCSSource.java index 9cd745e7d..4287b2a69 100644 --- a/src/main/java/io/cdap/plugin/gcp/gcs/source/GCSSource.java +++ b/src/main/java/io/cdap/plugin/gcp/gcs/source/GCSSource.java @@ -28,7 +28,6 @@ import io.cdap.cdap.api.annotation.MetadataProperty; import io.cdap.cdap.api.annotation.Name; import io.cdap.cdap.api.annotation.Plugin; -import io.cdap.cdap.api.exception.ErrorUtils; import io.cdap.cdap.etl.api.FailureCollector; import io.cdap.cdap.etl.api.PipelineConfigurer; import io.cdap.cdap.etl.api.batch.BatchSource; @@ -44,10 +43,10 @@ import io.cdap.plugin.format.plugin.AbstractFileSourceConfig; import io.cdap.plugin.format.plugin.FileSourceProperties; import io.cdap.plugin.gcp.common.GCPConnectorConfig; -import io.cdap.plugin.gcp.common.GCPErrorDetailsProvider; import io.cdap.plugin.gcp.common.GCPUtils; import io.cdap.plugin.gcp.common.GCSEmptyInputFormat; import io.cdap.plugin.gcp.crypto.EncryptedFileSystem; +import io.cdap.plugin.gcp.gcs.GCSErrorDetailsProvider; import io.cdap.plugin.gcp.gcs.GCSPath; import io.cdap.plugin.gcp.gcs.connector.GCSConnector; @@ -143,7 +142,7 @@ public void prepareRun(BatchSourceContext context) throws Exception { // set error details provider context.setErrorDetailsProvider( - new ErrorDetailsProviderSpec(GCPErrorDetailsProvider.class.getName())); + new ErrorDetailsProviderSpec(GCSErrorDetailsProvider.class.getName())); // super is called down here to avoid instantiating the lineage recorder with a null asset super.prepareRun(context);