Once you’ve defined your errors in the Goa DSL, the next step is to map these errors to appropriate transport-specific status codes. This ensures that clients receive meaningful and standardized responses based on the nature of the error. Goa allows you to define these mappings for different transport protocols, such as HTTP and gRPC, using the Response function within the DSL.
For HTTP transports, you use the HTTP function within your service or method definitions to map errors to specific HTTP status codes. This mapping ensures that when an error occurs, the client receives an HTTP response with the correct status code and error information.
Example
var _ = Service("divider", func() {
Error("DivByZero", func() {
Description("DivByZero is the error returned when the divisor is zero.")
})
HTTP(func() {
// Map the "DivByZero" error to HTTP 400 Bad Request
Response("DivByZero", StatusBadRequest)
})
Method("integral_divide", func() {
Error("HasRemainder", func() {
Description("HasRemainder is returned when an integer division has a remainder.")
})
HTTP(func() {
// Map the "HasRemainder" error to HTTP 417 Expectation Failed
Response("HasRemainder", StatusExpectationFailed)
})
// Additional method definitions...
})
Method("divide", func() {
// Method-specific definitions...
})
})
In this example:
DivByZero
: Mapped to HTTP status code 400 Bad Request.HasRemainder
: Mapped to HTTP status code 417 Expectation Failed.Within the HTTP function, you use the Response function to associate each error with an HTTP status code. The syntax is as follows:
Response("<ErrorName>", <HTTPStatusCode>, func() {
Description("<Optional description>")
})
<ErrorName>
: The name of the error as defined in the DSL.<HTTPStatusCode>
: The HTTP status code to map the error to.Description
: (Optional) A description of the response for documentation purposes.var _ = Service("divider", func() {
// Service-level errors
Error("DivByZero", func() {
Description("DivByZero is the error returned when the divisor is zero.")
})
HTTP(func() {
// Service-wide error mappings
Response("DivByZero", StatusBadRequest) // 400
})
Method("integral_divide", func() {
Description("Performs integer division and checks for remainders")
Payload(func() {
Field(1, "dividend", Int, "Number to be divided")
Field(2, "divisor", Int, "Number to divide by")
Required("dividend", "divisor")
})
Result(Int)
Error("HasRemainder", func() {
Description("HasRemainder is returned when an integer division has a remainder.")
})
HTTP(func() {
POST("/divide/integral")
// Method-specific error mapping
Response("HasRemainder", StatusExpectationFailed, func() { // 417
Description("Returned when the division results in a remainder")
})
})
})
Method("divide", func() {
Description("Performs floating-point division")
Payload(func() {
Field(1, "dividend", Float64, "Number to be divided")
Field(2, "divisor", Float64, "Number to divide by")
Required("dividend", "divisor")
})
Result(Float64)
Error("Overflow", func() {
Description("Overflow is returned when the result exceeds maximum value.")
})
HTTP(func() {
POST("/divide")
// Method-specific error mapping
Response("Overflow", StatusUnprocessableEntity, func() { // 422
Description("Returned when the division result exceeds maximum value")
})
})
})
})
This example demonstrates:
Service-Level Errors: Common errors that apply across methods:
DivByZero
: When attempting to divide by zeroMethod-Specific Errors: Each method defines its own specific errors:
integral_divide
: Handles remainder casesdivide
: Handles floating-point overflowHTTP Status Code Mapping:
Different Endpoints: Shows error mapping for two different division operations:
/divide/integral
for integer division/divide
for floating-point divisionThe mappings ensure that each error condition returns an appropriate HTTP status code that accurately reflects the nature of the error.
For gRPC transports, you use the GRPC function within your service or method definitions to map errors to specific gRPC status codes. This mapping ensures that when an error occurs, the client receives a gRPC response with the correct status code and error information.
Example
var _ = Service("divider", func() {
Error("DivByZero", func() {
Description("DivByZero is the error returned when the divisor is zero.")
})
GRPC(func() {
// Map the "DivByZero" error to gRPC status code InvalidArgument (3)
Response("DivByZero", CodeInvalidArgument)
})
Method("integral_divide", func() {
Error("HasRemainder", func() {
Description("HasRemainder is returned when an integer division has a remainder.")
})
GRPC(func() {
// Map the "HasRemainder" error to gRPC status code Unknown (2)
Response("HasRemainder", CodeUnknown)
})
// Additional method definitions...
})
Method("divide", func() {
// Method-specific definitions...
})
})
In this example:
DivByZero
: Mapped to gRPC status code InvalidArgument (code 3).HasRemainder
: Mapped to gRPC status code Unknown (code 2).Within the GRPC function, you use the Response function to associate each error with a gRPC status code. The syntax is as follows:
Response("<ErrorName>", Code<StatusCode>, func() {
Description("<Optional description>")
})
<ErrorName>
: The name of the error as defined in the DSL.Code<StatusCode>
: The gRPC status code to map the error to, prefixed with Code.Description
: (Optional) A description of the response for documentation purposes.Goa allows you to define both HTTP and gRPC mappings within the same service or method. This is particularly useful when your service supports multiple transports, ensuring that errors are appropriately mapped regardless of the transport protocol used by the client.
Example
var _ = Service("divider", func() {
Error("DivByZero", func() {
Description("DivByZero is returned when attempting to divide by zero.")
})
Method("divide", func() {
Payload(func() {
Field(1, "dividend", Float64, "Number to be divided")
Field(2, "divisor", Float64, "Number to divide by")
Required("dividend", "divisor")
})
Result(Float64)
HTTP(func() {
POST("/divide")
// Map division by zero to HTTP 422 Unprocessable Entity
Response("DivByZero", StatusUnprocessableEntity)
})
GRPC(func() {
// Map division by zero to INVALID_ARGUMENT
Response("DivByZero", CodeInvalidArgument)
})
})
})
In this example, the DivByZero
error is mapped to both:
Mapping errors to transport-specific status codes in Goa ensures that clients receive clear and appropriate responses based on the nature of the error encountered. By defining these mappings within the DSL, Goa automates the generation of the necessary code and documentation, maintaining consistency and reducing boilerplate. Whether you’re working with HTTP, gRPC, or both, Goa’s flexible error mapping capabilities empower you to build robust and user-friendly APIs.