xerror

package module
v0.0.0-...-8b3f6e0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 25, 2024 License: MIT Imports: 9 Imported by: 0

README

xerror

xerror is a powerful error handling library designed to enhance error management in applications that utilize both HTTP and gRPC APIs. With xerror, you can easily define and wrap errors with additional context, add runtime states for logging purposes, and seamlessly handle errors in both gRPC and HTTP environments. This library also enables you to hide sensitive data in error responses and provides flexible log level configuration.

By using xerror, you can streamline your error handling process and ensure consistent and compliant error responses. Whether you're building a microservice or a complex distributed system, xerror offers a comprehensive set of features to simplify error management and improve the reliability of your applications.

Don't let errors hinder the reliability and stability of your applications. Try xerror today and experience a new level of error handling sophistication.

The error model that serves as the foundation for xerrors is the Google Cloud APIs error model, which is documented in detail in the Google Cloud APIs error model documentation. This error model provides a robust and standardized approach to handling errors in applications. This is referred to as the google.rpc.status error model in this text.

What's Included

  • Simplifies error management in applications that utilize both HTTP and gRPC APIs.
  • Provides additional context and runtime states for error handling and logging purposes.
  • Enables hiding sensitive data in error responses.
  • Offers flexible log level configuration.
  • Ensures consistent and compliant error responses.
  • Streamlines error handling process.
  • Improves the reliability of applications.
  • Provides a comprehensive set of features for error management.
  • Aligns with the google.rpc.status error model.
  • Enhances error handling sophistication.
  • Prevents errors from hindering reliability and stability of applications.
  • Makes it easy to classify errors with a builtin error guide (usable from your code)

Usage

To get started, simply add a call to xerror.Init() in your main.go file and run go mod tidy. Then, you can leverage the various functionalities of xerror to handle errors effectively and efficiently.

The xerror.Init() call is used to initialize the xerror package. It sets a global value called "domain" that is used when adding an ErrorInfo detail to our error. The "domain" value should represent either the name of your service or its domain name. By setting the "domain" value globally, you don't have to specify it every time you add an ErrorInfo detail to your error. This simplifies the error handling process and ensures consistency throughout your application.

An example:

// main.go

import "github.com/tobbstr/xerror"

func main() {
    xerror.Init("pubsub.googleapis.com") // replace this string with your service name or service domain as in the example
}

Then you're all set! ✅

See the next sections for how to use it for different purposes.

XError Properties

When working with xerror, you can take advantage of the following properties:

  1. Error Model: xerror utilizes the google.rpc.status model to effectively return consistent errors from your application to API callers.
  2. Debug Info and Error Info: You have the option to remove debug info and error info details from the error response sent to API callers.
  3. Runtime State: Capture and log relevant information by storing key-value pairs in the runtime state. This allows for better error analysis and troubleshooting.
  4. Log Level: Set the log level for your application, ensuring that errors are logged at the appropriate severity level.
  5. Retries: Utilize the xerror API to easily determine if an error is retryable.

By leveraging these properties, you can enhance your error handling process and improve the overall reliability of your application. Remember to always consider the specific needs and requirements of your application when utilizing xerror.

Errors Originating Within Your System

This section provides guidance on working with errors that occur within your system. Whether it's validating arguments or checking preconditions, you are responsible for creating the root error and classifying it correctly according to the google.rpc.code definitions.

Error Constructors

The xerror library offers convenient constructors to initialize errors of different types. These types align with the google.rpc.code definitions, including:

  • INVALID_ARGUMENT
  • FAILED_PRECONDITION
  • OUT_OF_RANGE
  • UNAUTHENTICATED
  • PERMISSION_DENIED
  • NOT_FOUND
  • ABORTED
  • ALREADY_EXISTS
  • RESOURCE_EXHAUSTED
  • CANCELLED
  • DATA_LOSS
  • UNKNOWN
  • INTERNAL
  • NOT_IMPLEMENTED
  • UNAVAILABLE
  • DEADLINE_EXCEEDED

For example, to create an error of type INVALID_ARGUMENT, you can use the following constructor:

// validating arguments ...

if request.Age < 65 {
    return xerror.NewInvalidArgument("age", "This operation is reserved for people 65+")
}
Visual Overview of Error Types

To help you understand the organization of error types, a visual overview is provided. Some error types are specific to problems with the request, while others are related to server issues. Additionally, certain error types are nested within others for more specialized scenarios. Here is a simplified representation:

flowchart LR
subgraph "Problems with the Request"
    CANCELLED
    subgraph INVALID_ARGUMENT
        OUT_OF_RANGE
        NOT_FOUND
        DATA_LOSS
    end
    PERMISSION_DENIED
    UNAUTHENTICATED
end

subgraph "Problems with the Server"
    DATA_LOSS_2["DATA_LOSS"]
    subgraph FAILED_PRECONDITION
        ABORTED
        ALREADY_EXISTS
        RESOURCE_EXHAUSTED
    end
    UNKNOWN
    INTERNAL
    NOT_IMPLEMENTED
    UNAVAILABLE
    DEADLINE_EXCEEDED
end
Error Guide

Don't let error classification complexity hinder your development. With xerror's error guide (xerror.ErrorGuide()), confidently identify and classify errors, making your code more consistent, robust and maintainable. The xerror library provides a built-in error guide function that simplifies error classification in your code. This error guide assists you in accurately categorizing errors, ensuring proper handling and management. By leveraging this feature, you can streamline your error handling process and improve the reliability of your application.

While the error guide is a valuable resource, you can also take advantage of the convenient constructor functions such as xerror.NewNotFound() once you have a clear understanding of your requirements.

Feel free to explore the error guide and constructor functions to streamline your error handling process and ensure accurate error classification. Hopefully this information helps you effectively handle errors within your system.

Errors Originating From External gRPC APIs

When working with errors returned by gRPC APIs, the xgrpc package provides a convenient function called ErrorFrom(). This function allows you to keep the error status from the external system without mapping it into a specific xerror such as when constructing an xerror using the constructors in the xerror package. Here's an example:

// Example: Keeping the returned error status from the external call
resp, err := grpcClient.DoThat()
if err != nil {
    return xgrpc.ErrorFrom(err) // This xerror retains the error status
}

By using ErrorFrom(), you can handle errors from gRPC APIs effectively and maintain the original error status. Give it a try in your application!

Advanced Error Handling

In some cases, simply wrapping a returned error in an xerror may not be sufficient. You may need to inspect the error and handle different error types differently. For example, let's say your service needs to order more pencils when it runs out of stock. If the order fails due to being out of stock, your service should make another call to restock the pencils.

The google.rpc.status error model fully supports this use case. It leverages the ErrorInfo detail, which is included in the error status. The ErrorInfo detail serves the purpose of uniquely identifying errors, allowing for seamless propagation across multiple service hops. This enables edge services to effectively inspect and take appropriate action based on downstream errors. Rest assured, with the google.rpc.status error model, your error handling process will be robust, reliable, and efficient.

{
    "error": {
        "code": 8,
        "message": "The order couldn't be fulfilled. The requested item is out of stock",
        "status": "RESOURCE_EXHAUSTED",
        "details": [
            {
                "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                "reason": "OUT_OF_STOCK",
                "domain": "greatpencils.com",
                "metadata": {
                    "service": "order.greatpencils.com"
                }
            }
        ]
    }
}

The reason field (error.details[0].reason in this example) is designed to be examined for domain-specific errors. It is scoped to the domain and should be combined with the domain to distinguish between different services that share the same reason value.

Thankfully, the xerror library offers a convenient method for checking domain-specific errors, making it easier to handle and manage errors in your application.

resp, err := orderClientpb.OrderPencils()
if err != nil {
        xerr := xgrpc.ErrorFrom(err)
        if xerr.IsDomainError(order.Domain, order.ReasonOutOfStock) { 
                // handle the case when pencils are out of stock
                restockPencils()
        }
}

In the example above, the order service exports its domain (e.g., "order.greatpencils.com") and domain-specific reasons (as enums) for the check. You can refer to Google's enum definitions for inspiration.

By leveraging this advanced error handling technique, you can effectively handle different errors and ensure the smooth operation of your service.

Error Propagation Outside of Your Domain or Bounded Context

This section discusses the handling of errors when they need to be returned to callers of your service. It is important to consider the trustworthiness of the caller in such scenarios. Internal services within the same organization are often considered trusted, but if the caller is on a public network, such as the Internet, it may be necessary to exercise caution.

Trusted Callers

For trusted callers, your service has the flexibility to choose what error information to return.

Untrusted Callers

Untrusted callers can be categorized into two types:

  1. Applications, such as mobile apps or web apps, that belong to your organization but are running on untrusted networks. In this case, only the network is untrusted.
  2. Completely external code that calls your service. In this case, both the caller and the network it is running on are untrusted.

When dealing with untrusted callers, regardless of type, avoid including sensitive information like stack traces in error responses. The xerror library offers a convenient method to strip sensitive details from the google.rpc.status error model. Specifically, it removes "debug info" and "error info" details. "Debug info" can include stack traces and diagnostic information intended for developers to diagnose issues, while "error info" provides structured data about the error. Removing these details ensures your error responses are secure and do not expose unnecessary information.

resp, err := orderClientpb.OrderPencils()
if err != nil {
    return xgrpc.ErrorFrom(err).
        HideDetails() // Hides sensitive details before returning the error to the caller
}

For untrusted callers of type (1), the error may be propagated to the caller as-is, but it is still recommended to strip it of sensitive information. For untrusted callers of type (2), it is recommended to translate the error into a generic "internal server" error without any additional information. This can be achieved using the provided constructors mentioned in the error constructors section. See the example below.

resp, err := orderClientpb.OrderPencils()
if err != nil {
    return xerror.NewInternal(err).
        HideDetails() // Hides sensitive details before returning the error to the caller
}

Using xerrors in HTTP APIs

When integrating xerrors into your HTTP APIs, it's important to handle sensitive information appropriately before returning error responses to callers. The "debug info" and "error info" details may contain sensitive data that should be sanitized.

With xerror, you don't have to deal with multiple error models for your application and HTTP API. The xerror package provides a subpackage called xhttp, which includes a convenient function called RespondFailed(w http.ResponseWriter, err error).

TIP: Import the xhttp package using dot notation (import . "github.com/tobbstr/xerror/xhttp") so you don't have to write out the package name when invoking the RespondFailed function. This can make your code more concise and readable.

By passing an xerror to this function, it will automatically translate it into a JSON-formatted representation of a google.rpc.status error model. This simplifies error handling in your HTTP APIs and ensures consistent and standardized error responses. See the example below:

{
    "error": {
        "code": 10,
        "details": [
            {
                "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                "domain": "myservice.example.com",
                "metadata": {
                    "resource": "projects/123",
                    "service": "pubsub.googleapis.com"
                },
                "reason": "VERSION_MISMATCH"
            }
        ],
        "message": "optimistic concurrency control conflict: resource revision mismatch",
        "status": "ABORTED"
    }
}

Using xerrors in gRPC APIs

In addition to HTTP APIs, xerrors can also be utilized in gRPC APIs. The process involves registering an interceptor in the server, which allows for the seamless integration of xerrors in the endpoint implementations. After registering the interceptor, xerrors should be returned in endpoint implementations. The interceptor takes care of responding with a google.rpc.status error. This allows for seamless integration and enhances the error handling capabilities of your gRPC APIs, ensuring consistent and standardized error responses.

// main.go

import (
    "google.golang.org/grpc"
    "github.com/tobbstr/xerror/xgrpc"
)

func main() {
    // Create a gRPC server instance with the interceptor
    server := grpc.NewServer(
        grpc.UnaryInterceptor(xgrpc.UnaryXErrorInterceptor),
    )
}

Logging Errors in Your Application

When it comes to logging errors in your application, there are two key considerations. First, you want to ensure that all relevant details of the error are captured. Second, you need to determine the appropriate log level for the error.

Let's consider an example scenario. Suppose you are making a gRPC call to an external system, and the call fails. In this case, you want to create an error value that accurately captures what went wrong, along with any relevant details. This error value will then be propagated up the call stack until it reaches a point where it should be logged.

By effectively logging errors in your application, you can gain valuable insights into the root causes of failures and troubleshoot issues more efficiently. Additionally, logging errors at the appropriate log level ensures that you can prioritize and address them effectively.

Remember, logging errors is an essential practice for maintaining the reliability and stability of your application. So make sure to incorporate robust error logging mechanisms into your development process.

Capturing Runtime State

Capturing the runtime state is a useful technique when you want to preserve the values of relevant variables at the time an error occurs. This can provide valuable insights into the context and help with troubleshooting and debugging.

By including the runtime state in your error handling, you can easily identify the specific conditions that led to the error. This can be especially helpful when dealing with complex scenarios or hard-to-reproduce issues.

To capture the runtime state, you can include relevant variables or data structures in the error value itself. This ensures that the information is readily available when the error is logged or inspected.

For example, let's say you have a function that performs a calculation and encounters an error. Instead of just returning the error, you can create an xerror that includes the input parameters, intermediate results, or any other relevant data. This way, when the error is logged or reported, you have all the necessary information to understand what went wrong.

func PerformCalculation(input int) error {
    // Perform the calculation
    result, err := calculate(input)
    if err != nil {
        // Create an xerror value with the runtime state
        return xerror.NewInternal(err).
            AddVar("input": input).  // Adds the input to the runtime state
            AddVar("result", result) // Adds the intermediate result to the runtime state
    }
    // Continue with the rest of the code
    return nil
}

By capturing the runtime state in your error handling, you can enhance the effectiveness of your debugging process and improve the overall reliability of your application.

Logging xerrors

To effectively log xerrors in your application, you can follow these steps:

  1. Identify the location in your code where the error is furthest in the call stack.
  2. Initialize a new xerror using xerror.NewInternal(...) or any other appropriate constructor or helper function.
  3. Let the xerror bubble up the call stack, adding more context to it along the way using the xerror.Wrap() function.
  4. At the top of the call stack, use a logging library like zap to log the error.
  5. Extract the runtime state from the xerror using xerr.RuntimeState() and log it.
  6. Determine the log level based on the severity of the error using xerr.LogLevel().

Here's an example of how you can log an xerror using the zap library:

err := function_1()
if err != nil {
    xerr := xerror.From(err) // converts the err value into an xerror
    runtimeState := xerr.RuntimeState()
    zapFields := make([]zap.ZapField, len(runtimeState))
    for i, v := range runtimeState {
        zapFields[i] = zap.Any(v.Name, v.Value)
    }

    switch xerr.LogLevel() {
    case xerror.LogLevelInfo:
        logger.Info("invoking function_1()", zapFields...)

    // Handle other log levels...

    }
}

By following these steps, you can ensure that all relevant details of the xerror are captured and logged appropriately, helping you troubleshoot and debug issues more efficiently.

Remember, logging errors is an essential practice for maintaining the reliability and stability of your application. Incorporate robust error logging mechanisms into your development process to gain valuable insights into the root causes of failures.

Setting the Log Level

Setting the log level is straightforward. Here's an example:

return xerror.From(err).SetLogLevel(xerror.LogLevelWarn) // This sets a warning log level

Retries

When performing functions, methods, or external calls, failures can occur for various reasons. While some failures may not be worth retrying, others may be worth attempting again.

Retries can be categorized into two types: immediate retries and higher-level retries. Immediate retries involve retrying the failed call itself, while higher-level retries occur further up the call stack, potentially involving the retry of an entire transaction.

To simplify the retry process, the google.rpc.status error model supports these two categories. The xerror library offers two convenience functions that relieve developers from the burden of remembering which error codes allow for retries, and of which category.

The two provided methods are:

resp, err := orderClientpb.OrderPencils()
if err != nil {
    xerr := xgrpc.ErrorFrom(err)
    if xerr.IsDirectlyRetryable() {
        // implementation skipped for brevity
    } else if xerr.IsRetryableAtHigherLevel() {
        // implementation skipped for brevity
    }
}

Documentation

Overview

Package xerror provides a way to wrap errors with additional context and to add variables to the error that can be logged at a later time. It also provides a way to categorize errors into different kinds.

Index

Constants

This section is empty.

Variables

View Source
var ErrFailedToAddErrorDetails = errors.New("failed to add error details")

Functions

func DomainType

func DomainType(domain, reason string) string

DomainType returns a unique error type based on the domain and reason. This is used to enable switch-case statements.

func ErrorGuide

func ErrorGuide() errorGuide

ErrorGuide implements a decision tree that helps developers choose the right error type for their use case. Errors are not supposed to be created using this guide, although it is possible. Instead, use the guide to find the right error type and then create the error using the appropriate factory function.

IMPORTANT! Each method has a comment that explains when to use the error type. Please read the comments carefully before choosing an error type.

func Init

func Init(domain string)

Init initializes the package. It must called once, before creating any errors, and only be called at application startup-time. It is NOT thread-safe.

The domain is the logical grouping to which the "reason" belongs. See the Reason field in the unexported errorInfo struct for more information about the "reason". The error domain is typically the registered service name of the tool or product that generated the error. The domain must be a globally unique value.

  • Example: pubsub.googleapis.com

func Wrap

func Wrap(err error, msg string) error

Wrap wrap errors with a message to add more context to the error. It is used when receiving an error from a call that is already an Error instance and you want to add more context to the error.

Ex.

 err := pkg.Func() // returns an Error instance
 if err != nil {
	  return errors.Wrap(err, "more context to err")
 }

Types

type BadRequestViolation

type BadRequestViolation struct {
	// Field is a path that leads to a field in the request body. The value will be a
	// sequence of dot-separated identifiers that identify a protocol buffer
	// field.
	//
	// Consider the following:
	//
	//	message CreateContactRequest {
	//	  message EmailAddress {
	//	    enum Type {
	//	      TYPE_UNSPECIFIED = 0;
	//	      HOME = 1;
	//	      WORK = 2;
	//	    }
	//
	//	    optional string email = 1;
	//	    repeated EmailType type = 2;
	//	  }
	//
	//	  string full_name = 1;
	//	  repeated EmailAddress email_addresses = 2;
	//	}
	//
	// In this example, in proto `field` could take one of the following values:
	//
	//   - `full_name` for a violation in the `full_name` value
	//   - `email_addresses[1].email` for a violation in the `email` field of the
	//     first `email_addresses` message
	//   - `email_addresses[3].type[2]` for a violation in the second `type`
	//     value in the third `email_addresses` message.
	//
	// In JSON, the same values are represented as:
	//
	//   - `fullName` for a violation in the `fullName` value
	//   - `emailAddresses[1].email` for a violation in the `email` field of the
	//     first `emailAddresses` message
	//   - `emailAddresses[3].type[2]` for a violation in the second `type`
	//     value in the third `emailAddresses` message.
	Field string
	// Description is a description of why the request element is bad.
	Description string
}

BadRequestViolation is a message type used to describe a single bad request field.

type DebugInfo

type DebugInfo struct {
	// Additional debugging information provided by the server.
	Detail string
	// The stack trace entries indicating where the error occurred.
	StackEntries []string
}

Describes additional debugging info.

type Error

type Error struct {
	// contains filtered or unexported fields
}

func From

func From(err error) *Error

From returns an Error instance from an error. It's meant to be used in your application, at the place in the code where the error is logged.

If the error is not an Error instance, then it is an unexpected error and should be logged, so it can be discovered that there's code where the error isn't correctly handled.

func NewAborted

func NewAborted(opts ErrorInfoOptions) *Error

NewAborted creates a new Aborted error.

For when to use this, see the ErrorGuide function for more information.

func NewAlreadyExists

func NewAlreadyExists(info ResourceInfo) *Error

NewAlreadyExists creates a new AlreadyExists error.

For when to use this, see the ErrorGuide function for more information.

func NewAlreadyExistsBatch

func NewAlreadyExistsBatch(infos []ResourceInfo) *Error

NewAlreadyExistsBatch creates a new AlreadyExists error. This is the batch version that adds information about multiple resources that already exist.

For when to use this, see the ErrorGuide function for more information.

func NewCancelled

func NewCancelled() *Error

NewCancelled creates a new Cancelled error.

For when to use this, see the ErrorGuide function for more information.

func NewDeadlineExceeded

func NewDeadlineExceeded() *Error

NewDeadlineExceeded creates a new DeadlineExceeded error.

For when to use this, see the ErrorGuide function for more information.

func NewInternal

func NewInternal(err error) *Error

NewInternal creates a new Internal error.

For when to use this, see the ErrorGuide function for more information.

func NewInvalidArgument

func NewInvalidArgument(field, description string) *Error

NewInvalidArgument creates a new InvalidArgument error.

Parameters:

Field:
	A path that leads to a field in the request body. The value will be a
	sequence of dot-separated identifiers that identify a protocol buffer
	field.

	Consider the following:

	message CreateContactRequest {
	  message EmailAddress {
	    enum Type {
	      TYPE_UNSPECIFIED = 0;
	      HOME = 1;
	      WORK = 2;
	    }

	    optional string email = 1;
	    repeated EmailType type = 2;
	  }

	  string full_name = 1;
	  repeated EmailAddress email_addresses = 2;
	}

	In this example, in proto, `field` could take one of the following values:

	  - `full_name` for a violation in the `full_name` value
	  - `email_addresses[1].email` for a violation in the `email` field of the
	    first `email_addresses` message
	  - `email_addresses[3].type[2]` for a violation in the second `type`
	    value in the third `email_addresses` message.

	In JSON, the same values are represented as:

	  - `fullName` for a violation in the `fullName` value
	  - `emailAddresses[1].email` for a violation in the `email` field of the
	    first `emailAddresses` message
	  - `emailAddresses[3].type[2]` for a violation in the second `type`
	    value in the third `emailAddresses` message.

Description:
	A description of why the request element is bad. See the below examples:

	  - `fullName`: "The full name of the contact. It should include both first
	    and last names. Example: `John Doe`".
	  - `emailAddresses[3].type[2]`: "The type of the email address. It can be HOME,
	    WORK, or unspecified. Example: [HOME, WORK]".

For when to use this error type, see the ErrorGuide function for more information.

func NewInvalidArgumentBatch

func NewInvalidArgumentBatch(violations []BadRequestViolation) *Error

NewInvalidArgumentBatch creates a new InvalidArgument error. This is the batch version that adds multiple field violations.

For when to use this, see the ErrorGuide function for more information.

func NewNotFound

func NewNotFound(info ResourceInfo) *Error

NewNotFound creates a new NotFound error.

For when to use this, see the ErrorGuide function for more information.

func NewNotFoundBatch

func NewNotFoundBatch(infos []ResourceInfo) *Error

NewNotFoundBatch creates a new NotFound error. This is the batch version that adds information about multiple resources that were not found.

For when to use this, see the ErrorGuide function for more information.

func NewNotImplemented

func NewNotImplemented() *Error

NewNotImplemented creates a new NotImplemented error.

For when to use this, see the ErrorGuide function for more information.

func NewOutOfRange

func NewOutOfRange(field, description string) *Error

NewOutOfRange creates a new OutOfRange error.

Parameters: see NewInvalidArgument

For when to use this, see the ErrorGuide function for more information.

func NewOutOfRangeBatch

func NewOutOfRangeBatch(violations []BadRequestViolation) *Error

NewOutOfRangeBatch creates a new OutOfRange error. This is the batch version that adds multiple field violations.

For when to use this, see the ErrorGuide function for more information.

func NewPermissionDenied

func NewPermissionDenied(opts ErrorInfoOptions) *Error

NewPermissionDenied creates a new PermissionDenied error.

For when to use this, see the ErrorGuide function for more information.

func NewPreconditionFailure

func NewPreconditionFailure(subject, typ, description string) *Error

NewFailedPrecondition creates a new FailedPrecondition error.

Parameters:

Description:
	Is a description of how the precondition failed. Developers can use this
	description to understand how to fix the failure.  For example, "Terms of service not accepted".

Subject:
	Is the subject, relative to the type, that failed.
	For example, "google.com/cloud" relative to the "TOS" type would indicate
	which terms of service is being referenced.

Typ:
	Is the type of PreconditionFailure. It is recommended to use a service-specific
	enum type to define the supported precondition violation subjects. For
	example, "TOS" for "Terms of Service violation".

For when to use this error type, see the ErrorGuide function for more information.

func NewPreconditionFailureBatch

func NewPreconditionFailureBatch(violations []PreconditionViolation) *Error

NewFailedPreconditionBatch creates a new FailedPrecondition error. This is the batch version that adds multiple precondition violations.

func NewQuotaFailure

func NewQuotaFailure(subject, description string) *Error

NewQuotaFailure creates a new QuotaFailure error, which is a specialized version of a resource exhausted error.

Parameters:

Subject:
	The subject on which the quota check failed.
	For example, "clientip:<ip address of client>" or "project:<Google
	developer project id>".
Description:
	Description of how the quota check failed. Clients can use this
	description to find more about the quota configuration in the service's
	public documentation.
	For example: "Service disabled" or "Daily Limit for read operations
	exceeded".

For when to use this, see the ErrorGuide function for more information.

func NewQuotaFailureBatch

func NewQuotaFailureBatch(violations []QuotaViolation) *Error

NewQuotaFailureBatch creates a new QuotaFailure error. This is the batch version that adds multiple quota violations.

For when to use this, see the ErrorGuide function for more information.

func NewRequestDataLoss

func NewRequestDataLoss(opts ErrorInfoOptions) *Error

NewRequestDataLoss creates a new DataLoss error.

For when to use this, see the ErrorGuide function for more information.

func NewResourceExhausted

func NewResourceExhausted(opts ErrorInfoOptions) *Error

NewResourceExhausted creates a new ResourceExhausted error.

For when to use this, see the ErrorGuide function for more information.

func NewServerDataLoss

func NewServerDataLoss(err error) *Error

NewServerDataLoss creates a new DataLoss error.

For when to use this, see the ErrorGuide function for more information.

func NewUnauthenticated

func NewUnauthenticated(opts ErrorInfoOptions) *Error

NewUnauthenticated creates a new Unauthenticated error.

For when to use this, see the ErrorGuide function for more information.

func NewUnavailable

func NewUnavailable(err error) *Error

NewUnavailable creates a new Unavailable error.

For when to use this, see the ErrorGuide function for more information.

func NewUnknown

func NewUnknown(err error) *Error

NewUnknown creates a new Unknown error.

For when to use this, see the ErrorGuide function for more information.

func (*Error) AddBadRequestViolations

func (xerr *Error) AddBadRequestViolations(violations []BadRequestViolation) *Error

AddBadRequestViolations adds a list of bad request violations to the error details. If the error details already contain bad request violations, the new ones are appended to the existing ones.

It is recommended to include a bad request violation for the following error types:

  • INVALID_ARGUMENT
  • OUT_OF_RANGE

See: https://cloud.google.com/apis/design/errors#error_payloads

func (*Error) AddPreconditionViolations

func (xerr *Error) AddPreconditionViolations(violations []PreconditionViolation) *Error

AddPreconditionViolations adds a list of precondition violations to the error details. If the error details already contain precondition violations, the new ones are appended to the existing ones.

func (*Error) AddQuotaViolations

func (xerr *Error) AddQuotaViolations(violations []QuotaViolation) *Error

AddQuotaViolations adds a list of quota violations to the error details. If the error details already contain quota violations, the new ones are appended to the existing ones.

func (*Error) AddResourceInfos

func (xerr *Error) AddResourceInfos(infos []ResourceInfo) *Error

AddResourceInfos adds resource info details to the error details. If the error details already contain a resource info detail, it is overwritten.

It is recommended to include a resource info detail for the following error types:

  • NOT_FOUND
  • ALREADY_EXISTS

See: https://cloud.google.com/apis/design/errors#error_payloads

func (*Error) AddVar

func (xerr *Error) AddVar(name string, value any) *Error

AddVar adds a variable to the runtime state.

func (*Error) AddVars

func (xerr *Error) AddVars(vars ...Var) *Error

AddVars adds multiple variables to the runtime state.

func (*Error) BadRequestViolations

func (xerr *Error) BadRequestViolations() []BadRequestViolation

BadRequestViolations returns a list of bad request violations. If the error details do not contain bad request violations, it returns nil.

func (*Error) DebugInfo

func (xerr *Error) DebugInfo() Optional[DebugInfo]

DebugInfo returns the error info details. If the error details do not contain error info details, it returns an invalid optional.

func (*Error) DomainType

func (xerr *Error) DomainType() string

DomainType returns a unique error type based on the domain and reason. This is used to enable switch-case statements.

Ex.

 err := othersystempb.SomeMethod(ctx, req)
 if err != nil {
	 xerr := grpc.XErrorFrom(err)
	 switch xerr.DomainType() {
	 case xerror.DomainType(othersystemerror.Domain, othersystemerror.NO_STOCK):
		 requestMoreStock() // decision based on the error type

func (*Error) Error

func (xerr *Error) Error() string

func (*Error) ErrorInfo

func (xerr *Error) ErrorInfo() Optional[ErrorInfo]

ErrorInfo returns the error info details. If the error details do not contain error info details, it returns an invalid optional.

func (*Error) HideDetails

func (xerr *Error) HideDetails() *Error

HideDetails marks the error as having hidden details. This is useful when you want to hide the details of the error from external callers. Effectively, this means that the "debug info" and "error info" details are removed from the error when returned to the caller. For this to work, the server has to use the implementation-specific functionality such as the unary interceptor for gRPC.

func (*Error) IsDetailsHidden

func (xerr *Error) IsDetailsHidden() bool

IsDetailsHidden returns true if the error details are hidden, otherwise it returns false.

func (*Error) IsDirectlyRetryable

func (xerr *Error) IsDirectlyRetryable() bool

IsDirectlyRetryable returns true if the call that caused the error is directly retryable, otherwise it returns false. Retries should be attempted using an exponential backoff strategy.

func (*Error) IsDomainError

func (xerr *Error) IsDomainError(domain, reason string) bool

IsDomainError compares the error with the provided domain-specific error details (the domain and reason). The reason is machine-readable and most importantly, it is unique within a particular domain of errors. This method is used to check if a returned error is a particular domain-specific error. This is useful when decisions need to be made based on the error type.

Note! Sometimes, decisions can be made based on the status code alone, but when that is not granular enough, the domain and reason should be used to make decisions.

Ex.

 err := othersystempb.SomeMethod(ctx, req)
 if err != nil {
	 xerr := xgrpc.ErrorFrom(err)
	 if xerr.IsDomainError(othersystem.Domain, othersystem.NO_STOCK) {
		 requestMoreStock() // decision based on the error type
	 }
 }

func (*Error) IsRetryableAtHigherLevel

func (xerr *Error) IsRetryableAtHigherLevel() bool

IsRetryableAtHigherLevel returns true if the call that caused the error cannot be directly retried, but instead should be retried at a higher level in the system. Example: an optimistic concurrency error in a database transaction, where the whole transaction should be retried, not just the failing part.

func (*Error) LogLevel

func (xerr *Error) LogLevel() LogLevel

LogLevel returns the log level of the error.

func (*Error) MarshalJSON

func (xerr *Error) MarshalJSON() ([]byte, error)

MarshalJSON marshals the error to JSON. This is only useful for testing purposes to be able to generate golden files to be able to inspect the error in a human-readable format.

func (*Error) PreconditionViolations

func (xerr *Error) PreconditionViolations() []PreconditionViolation

PreconditionsViolations returns a list of precondition violations. If the error details do not contain precondition violations, it returns nil.

func (*Error) QuotaViolations

func (xerr *Error) QuotaViolations() []QuotaViolation

QuotaViolations returns a list of quota violations. If the error details do not contain quota violations, it returns nil.

func (*Error) RemoveSensitiveDetails

func (xerr *Error) RemoveSensitiveDetails() *Error

RemoveSensitiveDetails removes sensitive details from the error. This is useful when you want to return the error to the client, but you don't want to expose sensitive details such as debug info or error info.

func (*Error) ResourceInfos

func (xerr *Error) ResourceInfos() []ResourceInfo

ResourceInfos returns a list of resource info details. If the error details do not contain resource info details, it returns nil.

func (*Error) RuntimeState

func (xerr *Error) RuntimeState() []Var

RuntimeState returns the runtime state of the error. This is used when you want to log the circumstances when the error was encountered.

func (*Error) SetDebugInfo

func (xerr *Error) SetDebugInfo(detail string, stackEntries []string) *Error

SetDebugInfoDetail sets debug info detail to the error details. If the error details already contain a debug info detail, it is overwritten. If the detail is empty, the operation is a no-op.

It is NOT recommended to include a debug info detail since it'll be returned to the caller. If you need to log debug info, use the runtime state instead (the AddVar() and AddVars() methods). It's is however possible to include a debug info detail and still not return it to the caller by calling the HideDetails() method.

func (*Error) SetErrorInfo

func (xerr *Error) SetErrorInfo(domain, reason string, metadata map[string]any) *Error

SetErrorInfo sets error info details to the error details. If the error details already contain error info details, they are overwritten. If the domain is empty, the domain is set to the package's default domain which should be set at startup-time by calling the Init() function.

It is recommended to include an error info detail for the following error types:

  • UNAUTHENTICATED
  • PERMISSION_DENIED
  • ABORTED

See: https://cloud.google.com/apis/design/errors#error_payloads

func (*Error) SetLogLevel

func (xerr *Error) SetLogLevel(level LogLevel) *Error

SetLogLevel sets the log level of the error.

func (*Error) SetStatus

func (xerr *Error) SetStatus(s *status.Status) *Error

SetStatus sets the status of the error.

func (*Error) ShowDetails

func (xerr *Error) ShowDetails() *Error

ShowDetails marks the error as having shown details. This is the inverse of HideDetails.

func (*Error) Status

func (xerr *Error) Status() *status.Status

Status returns a copy of the status contained in the error.

func (*Error) StatusCode

func (xerr *Error) StatusCode() codes.Code

func (*Error) StatusMessage

func (xerr *Error) StatusMessage() string

func (*Error) StatusProto

func (xerr *Error) StatusProto() *spb.Status

StatusProto returns the status proto contained in the error.

type ErrorInfo

type ErrorInfo struct {
	// Domain is the logical grouping to which the "reason" belongs. The error domain is typically the registered
	// service name of the tool or product that generated the error. The domain must be a globally unique value.
	Domain string
	// Reason is a short snake_case description of why the error occurred. Error reasons are unique within a particular
	// domain of errors. The application should define an enum of error reasons.
	//
	// The reason should have these properties:
	//	- Be meaningful enough for a human reader to understand what the reason refers to.
	//	- Be unique and consumable by machine actors for automation.
	//	- Example: CPU_AVAILABILITY
	//	- Distill your error message into its simplest form. For example, the reason string could be one of the
	//	  following text examples in UPPER_SNAKE_CASE: UNAVAILABLE, NO_STOCK, CHECKED_OUT, AVAILABILITY_ERROR, if your
	//	  error message is:
	//	  The Book, "The Great Gatsby", is unavailable at the Library, "Garfield East". It is expected to be available
	//	  again on 2199-05-13.
	Reason string
	// Metadata is additional structured details about this error, which should provide important context for clients
	// to identify resolution steps. Keys should be in lower camel-case, and be limited to 64 characters in length.
	// When identifying the current value of an exceeded limit, the units should be contained in the key, not the value.
	//
	// Example: {"vmType": "e2-medium", "attachment": "local-ssd=3,nvidia-t4=2", "zone": us-east1-a"}
	Metadata map[string]string
}

ErrorInfo describes the cause of the error with structured details.

Example of an error when contacting the "pubsub.googleapis.com" API when it is not enabled:

{ "reason": "API_DISABLED",
  "domain": "googleapis.com",
  "metadata": {
    "resource": "projects/123",
    "service": "pubsub.googleapis.com"
  }
}

This response indicates that the pubsub.googleapis.com API is not enabled. Example of an error that is returned when attempting to create a Spanner instance in a region that is out of stock:

{ "reason": "STOCKOUT",
  "domain": "spanner.googleapis.com",
  "metadata": {
    "availableRegions": "us-central1,us-east2"
  }
}

type ErrorInfoOptions

type ErrorInfoOptions struct {
	// Error is the error that occurred.
	Error error
	// Reason is a short snake_case description of why the error occurred. Error reasons are unique within a particular
	// domain of errors. The application should define an enum of error reasons.
	//
	// The reason should have these properties:
	//  - Be meaningful enough for a human reader to understand what the reason refers to.
	//  - Be unique and consumable by machine actors for automation.
	//  - Example: CPU_AVAILABILITY
	//  - Distill your error message into its simplest form. For example, the reason string could be one of the
	//    following text examples in UPPER_SNAKE_CASE: UNAVAILABLE, NO_STOCK, CHECKED_OUT, AVAILABILITY_ERROR, if your
	//    error message is:
	//    The Book, "The Great Gatsby", is unavailable at the Library, "Garfield East". It is expected to be available
	//    again on 2199-05-13.
	Reason string
	// Metadata is additional structured details about this error, which should provide important context for clients
	// to identify resolution steps. Keys should be in lower camel-case, and be limited to 64 characters in length.
	// When identifying the current value of an exceeded limit, the units should be contained in the key, not the value.
	//
	// Example: {"vmType": "e2-medium", "attachment": "local-ssd=3,nvidia-t4=2", "zone": us-east1-a"}
	Metadata map[string]any
}

type LogLevel

type LogLevel uint8

LogLevel is used to control the way the error is logged. For example as an error, warning, notice etc.

const (
	LogLevelUnspecified LogLevel = iota
	LogLevelDebug
	LogLevelInfo
	LogLevelWarn
	LogLevelError
)

type Optional

type Optional[T any] struct {
	// Value is the value that may or may not be present.
	Value T
	// Valid is true if the value is present, otherwise it is false.
	Valid bool
}

Optional is a type used to model a value that may or may not be present. It is used to model the return value of a function that may return a value or not. It is used to avoid returning nil values.

type PreconditionViolation

type PreconditionViolation struct {
	// Description is a description of how the precondition failed. Developers can use this
	// description to understand how to fix the failure.
	//
	// For example: "Terms of service not accepted".
	Description string
	// Subject is the subject, relative to the type, that failed.
	// For example, "google.com/cloud" relative to the "TOS" type would indicate
	// which terms of service is being referenced.
	Subject string
	// Typ is the type of PreconditionFailure. It's recommended using a service-specific
	// enum type to define the supported precondition violation subjects. For
	// example, "TOS" for "Terms of Service violation".
	Typ string
}

PreconditionViolation is a message type used to describe a single precondition failure.

type QuotaViolation

type QuotaViolation struct {
	//Subject on which the quota check failed.
	// For example, "clientip:<ip address of client>" or "project:<Google
	// developer project id>".
	Subject string
	// Descriptions is a description of how the quota check failed. Clients can use this
	// description to find more about the quota configuration in the service's
	// public documentation, or find the relevant quota limit to adjust through
	// developer console.
	//
	// For example: "Service disabled" or "Daily Limit for read operations
	// exceeded".
	Description string
}

QuotaViolation is a message type used to describe a single quota violation. For example, a daily quota or a custom quota that was exceeded.

type ResourceInfo

type ResourceInfo struct {
	// Description describes what error is encountered when accessing this resource.
	// For example, updating a cloud project may require the `writer` permission
	// on the developer console project.
	Description string
	// ResourceName is the name of the resource being accessed.  For example, a shared calendar
	// name: "example.com_4fghdhgsrgh@group.calendar.google.com", if the current
	// error is
	// [google.rpc.Code.PERMISSION_DENIED][google.rpc.Code.PERMISSION_DENIED].
	ResourceName string
	// ResourceType is a name for the type of resource being accessed, e.g. "sql table",
	// "cloud storage bucket", "file", "Google calendar"; or the type URL
	// of the resource: e.g. "type.googleapis.com/google.pubsub.v1.Topic".
	ResourceType string
	// Owner is the owner of the resource (optional).
	// For example, "user:<owner email>" or "project:<Google developer project
	// id>".
	Owner string
}

ResourceInfo describes the resource that is being accessed.

type Var

type Var struct {
	Name  string
	Value any
}

Var models what the circumstances were when the error was encountered and is used to provide additional context to the error. Its purpose is to be logged and thereby give context to the error in the logs.

type WrappedError

type WrappedError struct {
	Msg string
	Err error
}

WrappedError is a model that makes it easy to add more context to an error as it is passed up the call stack.

func (*WrappedError) AddVar

func (wr *WrappedError) AddVar(name string, value any) *WrappedError

AddVar adds runtime state information to the wrapped Error instance, if there is one.

func (*WrappedError) AddVars

func (wr *WrappedError) AddVars(vars ...Var) *WrappedError

AddVars adds multiple runtime state information to the wrapped Error instance, if there is one.

func (*WrappedError) Error

func (wr *WrappedError) Error() string

func (*WrappedError) Unwrap

func (wr *WrappedError) Unwrap() error

Unwrap returns the directly wrapped error.

func (*WrappedError) XError

func (wr *WrappedError) XError() *Error

XError returns the Error instance that is wrapped by the WrappedError instance, if there is one. Otherwise, it returns nil.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL