Coder Social home page Coder Social logo

How do we handle errors? about appsync-resolvers HOT 12 OPEN

sbstjn avatar sbstjn commented on June 16, 2024 8
How do we handle errors?

from appsync-resolvers.

Comments (12)

jonsaw avatar jonsaw commented on June 16, 2024 4

Got it to work based on the example found on AWS.

First, by having a custom error:

type ErrorHandler struct {
	Type    string `json:"error_type"`
	Message string `json:"error_message"`
}

And then returning an interface{} in the handler:

// CreateProject handler
func CreateProject(project models.Project) (interface{}, error) {
	now := time.Now()
	project.CreatedAt = &now
	project.UpdatedAt = &now

	err := project.Validate()
	if err != nil {
		handledError := &ErrorHandler{
			Type:    "VALIDATION_ERROR",
			Message: err.Error(),
		}
		return handledError, nil
	}

	err = project.Upsert()
	if err != nil {
		// Unhandled error
		return nil, err
	}

	return &project, nil
}

With a condition in the resolver's mapping-template:

#if( $context.result && $context.result.error_message )
    $utils.error($context.result.error_message, $context.result.error_type, $context.result)
#else
    $util.toJson($context.result)
#end

Not sure if this is the best way because we lose type checking in the handler.

from appsync-resolvers.

jonsaw avatar jonsaw commented on June 16, 2024 2

How about introducing the concept of middleware? Similar to how it's done with Goji or Gorilla.

package main

import (
	// removed
)

var (
	r = resolvers.New()
)

func errorMiddleware(h resolvers.Handler) resolvers.Handler {
	fn := func(in interface{}, err error) {
		if errData, ok := err.(*GraphQLError); ok {
			// hijack return if error is present
			h.Serve(errData, nil)
			return
		}

		// continue as per usual...
		h.Serve(in, err)
	}
	return resolvers.HandlerFunc(fn)
}

func init() {
	r.Use(errorMiddleware)
	// or chain other middleware ...
	// r.Use(loggerMiddleware)

	r.Add("create.project", handlers.CreateProject)
}

func main() {
	lambda.Start(r.Handle)
}

from appsync-resolvers.

sbstjn avatar sbstjn commented on June 16, 2024 1

Updating the data model with an optional error is a common practice, but somehow feels wrong. I have seen this a few times …

I'd rather update to general error handling and check if the returned error has a "Type" and "Message", and format the error response as needed.

Let's stick to best practices in Go for the resolvers, and use the package/lib for mapping to AWS magic 😂

from appsync-resolvers.

sbstjn avatar sbstjn commented on June 16, 2024

Good question 👍

Do you know if AppSync supports different error types? Is there any documentation available?

It might be even possible, that the error occurred before the Lambda function was involved at all. Seems like you missed a required attribute Name in your updateProject mutation? Required fields are validated/checked using the GraphQL schema, the request might be rejected before invoking the Lambda function …

from appsync-resolvers.

jonsaw avatar jonsaw commented on June 16, 2024

Another option would be to include ErrorType and ErrorMessage in the model:

// Project model structure
type Project struct {
	ProjectID        string     `json:"project_id"`
	Featured         bool       `json:"featured,omitempty"`
	ImageSrc050      string     `json:"image_src_050,omitempty"`
	ImageSrc100      string     `json:"image_src_100,omitempty"`
	Location         string     `json:"location,omitempty"`
	ShortName        string     `json:"short_name,omitempty"`
	ShortDescription string     `json:"short_description,omitempty"`
	Name             string     `json:"name"`
	Description      string     `json:"description"`
	CreatedBy        string     `json:"created_by"`
	CreatedAt        *time.Time `json:"created_at"`
	UpdatedAt        *time.Time `json:"updated_at"`

	ErrorType    utils.ErrorType `json:"error_type,omitempty"`
	ErrorMessage string          `json:"error_message,omitempty"`
}

And return *models.Project in the handler as per usual without losing the type.

We just need to add the condition in the resolver's mapping-template.

from appsync-resolvers.

jonsaw avatar jonsaw commented on June 16, 2024

Based on your feedback, something like this?

Handler:

package handlers

// imports removed

func CreateProject(project models.Project) (interface{}, error) {
	now := time.Now()
	project.CreatedAt = &now
	project.UpdatedAt = &now

	err := project.Validate()
	if err != nil {
		return errortypes.ErrorHandler(err)
	}

	err = project.Upsert()
	if err != nil {
		return errortypes.ErrorHandler(err)
	}

	return &project, nil
}

Resolver:

#if( $context.result && $context.result.error_message )
    $util.error($context.result.error_message, $context.result.error_type, $context.result.error_data)
#else
    $util.toJson($context.result)
#end

Example model:

package models

// imports removed

type Project struct {
	Name string `json:"name"`
}

func (project *Project) Validate() error {
	if project.Name == "" {
		return errortypes.New(errortypes.BadRequest, "Name required", project)
	}
	return nil
}

// other methods...

Error utility:

package errortypes

// imports removed

type ErrorType int

const (
	BadRequest ErrorType = iota
)

func (e ErrorType) String() string {
	return errorsID[e]
}

var errorsID = map[ErrorType]string{
	BadRequest: "BAD_REQUEST",
}

var errorsName = map[string]ErrorType{
	"BAD_REQUEST": BadRequest,
}

func (e *ErrorType) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString(`"`)
	buffer.WriteString(errorsID[*e])
	buffer.WriteString(`"`)
	return buffer.Bytes(), nil
}

func (e *ErrorType) UnmarshalJSON(b []byte) error {
	var s string
	err := json.Unmarshal(b, &s)
	if err != nil {
		return err
	}
	*e = errorsName[s]
	return nil
}

type GraphQLError struct {
	Type    ErrorType   `json:"error_type"`
	Message string      `json:"error_message"`
	Data    interface{} `json:"error_data"`
}

func (e *GraphQLError) Error() string {
	return e.Message
}

func New(t ErrorType, m string, d interface{}) *GraphQLError {
	return &GraphQLError{
		Type:    t,
		Message: m,
		Data:    d,
	}
}

func ErrorHandler(err error) (interface{}, error) {
	if errData, ok := err.(*GraphQLError); ok {
		return errData, nil
	}
	return nil, err
}

from appsync-resolvers.

jonsaw avatar jonsaw commented on June 16, 2024

Dabbled at the idea of having a middleware. This would allow users to further extend this awesome package. Still prelim, but you can find some example applications in this fork.

from appsync-resolvers.

tanduong avatar tanduong commented on June 16, 2024

I always see "errorInfo" to be null in the error response. Has anyone figured a way for custom error?

from appsync-resolvers.

tanduong avatar tanduong commented on June 16, 2024

I found the answer: https://stackoverflow.com/a/53495843/2724342

from appsync-resolvers.

alkleela avatar alkleela commented on June 16, 2024

the above link doesn't help in having the actual custom error message set at the AWS lambda java handler function, instead I get "message": "java.lang.RuntimeException",
example response :{
"data": {
"smalltournaments": null
},
"errors": [
{
"path": [
"smalltournaments"
],
"data": null,
"errorType": "Lambda:Unhandled",
"errorInfo": null,
"locations": [
{
"line": 30,
"column": 6,
"sourceName": null
}
],
"message": "java.lang.RuntimeException"
}
]
}
handler response json : {"cause":{"stackTrace":[....],"message":"Error Message for A101",
"localizedMessage":"Error Message for A101"}

from appsync-resolvers.

awsed avatar awsed commented on June 16, 2024

Could you please confirm if the documented solution works out for you?

https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-lambda.html#lambda-mapping-template-bypass-errors

from appsync-resolvers.

sescotti avatar sescotti commented on June 16, 2024

hi I ran into the same issue as the one described above. I'm using a js lambda resolver that simply throws an exception.

Lambda output:

{
  "errorType": "SampleError",
  "errorMessage": "an error message",
  "trace": [
    "SampleError: an error message",
    "    at LambdaHandler.handleEvent [as cb] (/var/task/dist/app/sample/handlers/get.js:10:11)",
    "    at Runtime.handler (/var/task/dist/app/handlers/types.js:18:25)",
    "    at Runtime.handleOnceNonStreaming (file:///var/runtime/index.mjs:1085:29)"
  ]
}

Response mapping template:

#if($ctx.error)
     $util.error($ctx.error.message, $ctx.error.type, $ctx, $ctx)
#end
$util.toJson($ctx.result)

AppSync output:

{
  ...
  "errors": [
    {
      "path": [
        "getSample"
      ],
      "data": {
        "id": null
      },
      "errorType": "Lambda:Unhandled",
      "message": "an error message"
   ...
}

Request mapping template (tried with both 2018-05-29 and 2017-02-28 template versions, same result)

{
  "version" : "2018-05-29",
  "operation": "Invoke",
  "payload": $util.toJson($context)
}

Now the weird thing is that if I remove the response mapping template, I get the correct error name:

{
   ...
  "errors": [
     ...
      "data": null,
      "errorType": "SampleError",
      "errorInfo": null,
      "locations": [
        {
          "line": 2,
          "column": 3,
          "sourceName": null
        }
      ],
      "message": "an error message"
    }
  ]
}

am I missing something?

from appsync-resolvers.

Related Issues (4)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.