// ErrorHandler turns a Go error into an HTTP response. It should be placed in the middleware chain // below the logger middleware so the logger properly logs the HTTP response. ErrorHandler // understands instances of goa.Error and returns the status and response body embodied in them, // it turns other Go error types into a 500 internal error response. // If verbose is false the details of internal errors is not included in HTTP responses. func ErrorHandler(service *goa.Service, verbose bool) goa.Middleware { return func(h goa.Handler) goa.Handler { return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { e := h(ctx, rw, req) if e == nil { return nil } status := http.StatusInternalServerError var respBody interface{} if err, ok := e.(*goa.Error); ok { status = err.Status respBody = err goa.ContextResponse(ctx).ErrorCode = err.Code rw.Header().Set("Content-Type", goa.ErrorMediaIdentifier) } else { respBody = e.Error() rw.Header().Set("Content-Type", "text/plain") } if status >= 500 && status < 600 { reqID := ctx.Value(reqIDKey) if reqID == nil { reqID = shortID() ctx = context.WithValue(ctx, reqIDKey, reqID) } goa.LogError(ctx, "uncaught error", "id", reqID, "msg", respBody) if !verbose { rw.Header().Set("Content-Type", goa.ErrorMediaIdentifier) respBody = goa.ErrInternal("internal error [%s]", reqID) } } return service.Send(ctx, status, respBody) } } }
// Update runs the update action. func (c *TrackerqueryController) Update(ctx *app.UpdateTrackerqueryContext) error { result := application.Transactional(c.db, func(appl application.Application) error { toSave := app.TrackerQuery{ ID: ctx.ID, Query: ctx.Payload.Query, Schedule: ctx.Payload.Schedule, TrackerID: ctx.Payload.TrackerID, } tq, err := appl.TrackerQueries().Save(ctx.Context, toSave) if err != nil { cause := errs.Cause(err) switch cause.(type) { case remoteworkitem.BadParameterError, remoteworkitem.ConversionError: jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrBadRequest(err.Error())) return ctx.BadRequest(jerrors) default: jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrInternal(err.Error())) return ctx.InternalServerError(jerrors) } } return ctx.OK(tq) }) c.scheduler.ScheduleAllQueries() return result }
// Create runs the create action. func (c *WorkItemLinkTypeController) Create(ctx *app.CreateWorkItemLinkTypeContext) error { // WorkItemLinkTypeController_Create: start_implement // Convert payload from app to model representation model := link.WorkItemLinkType{} in := app.WorkItemLinkTypeSingle{ Data: ctx.Payload.Data, } err := link.ConvertLinkTypeToModel(in, &model) if err != nil { jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrBadRequest(err.Error())) return ctx.BadRequest(jerrors) } return application.Transactional(c.db, func(appl application.Application) error { linkType, err := appl.WorkItemLinkTypes().Create(ctx.Context, model.Name, model.Description, model.SourceTypeName, model.TargetTypeName, model.ForwardName, model.ReverseName, model.Topology, model.LinkCategoryID) if err != nil { jerrors, httpStatusCode := jsonapi.ErrorToJSONAPIErrors(err) return ctx.ResponseData.Service.Send(ctx.Context, httpStatusCode, jerrors) } // Enrich linkCtx := newWorkItemLinkContext(ctx.Context, appl, c.db, ctx.RequestData, ctx.ResponseData, app.WorkItemLinkTypeHref) err = enrichLinkTypeSingle(linkCtx, linkType) if err != nil { jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrInternal("Failed to enrich link type: %s", err.Error())) return ctx.InternalServerError(jerrors) } ctx.ResponseData.Header().Set("Location", app.WorkItemLinkTypeHref(linkType.Data.ID)) return ctx.Created(linkType) }) // WorkItemLinkTypeController_Create: end_implement }
// List runs the list action. func (c *IdentityController) List(ctx *app.ListIdentityContext) error { return application.Transactional(c.db, func(appl application.Application) error { result, err := appl.Identities().List(ctx.Context) if err != nil { jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrInternal(fmt.Sprintf("Error listing identities: %s", err.Error()))) return ctx.InternalServerError(jerrors) } return ctx.OK(result) }) }
// Show runs the show action. func (c *WorkItemLinkCategoryController) Show(ctx *app.ShowWorkItemLinkCategoryContext) error { return application.Transactional(c.db, func(appl application.Application) error { res, err := appl.WorkItemLinkCategories().Load(ctx.Context, ctx.ID) if err != nil { jerrors, httpStatusCode := jsonapi.ErrorToJSONAPIErrors(err) return ctx.ResponseData.Service.Send(ctx.Context, httpStatusCode, jerrors) } linkCtx := newWorkItemLinkContext(ctx.Context, appl, c.db, ctx.RequestData, ctx.ResponseData, app.WorkItemLinkCategoryHref) err = enrichLinkCategorySingle(linkCtx, res) if err != nil { jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrInternal("Failed to enrich link category: %s", err.Error())) return ctx.InternalServerError(jerrors) } return ctx.OK(res) }) }
// Create runs the create action. func (c *WorkItemLinkCategoryController) Create(ctx *app.CreateWorkItemLinkCategoryContext) error { return application.Transactional(c.db, func(appl application.Application) error { cat, err := appl.WorkItemLinkCategories().Create(ctx.Context, ctx.Payload.Data.Attributes.Name, ctx.Payload.Data.Attributes.Description) if err != nil { jerrors, httpStatusCode := jsonapi.ErrorToJSONAPIErrors(err) return ctx.ResponseData.Service.Send(ctx.Context, httpStatusCode, jerrors) } linkCtx := newWorkItemLinkContext(ctx.Context, appl, c.db, ctx.RequestData, ctx.ResponseData, app.WorkItemLinkCategoryHref) err = enrichLinkCategorySingle(linkCtx, cat) if err != nil { jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrInternal("Failed to enrich link category: %s", err.Error())) return ctx.InternalServerError(jerrors) } ctx.ResponseData.Header().Set("Location", app.WorkItemLinkCategoryHref(cat.Data.ID)) return ctx.Created(cat) }) }
// Show runs the show action. func (c *SearchController) Show(ctx *app.ShowSearchContext) error { var offset int var limit int offset, limit = computePagingLimts(ctx.PageOffset, ctx.PageLimit) // ToDo : Keep URL registeration central somehow. hostString := ctx.RequestData.Host if hostString == "" { hostString = configuration.GetHTTPAddress() } urlRegexString := fmt.Sprintf("(?P<domain>%s)(?P<path>/work-item/list/detail/)(?P<id>\\d*)", hostString) search.RegisterAsKnownURL(search.HostRegistrationKeyForListWI, urlRegexString) urlRegexString = fmt.Sprintf("(?P<domain>%s)(?P<path>/work-item/board/detail/)(?P<id>\\d*)", hostString) search.RegisterAsKnownURL(search.HostRegistrationKeyForBoardWI, urlRegexString) return application.Transactional(c.db, func(appl application.Application) error { //return transaction.Do(c.ts, func() error { result, c, err := appl.SearchItems().SearchFullText(ctx.Context, ctx.Q, &offset, &limit) count := int(c) if err != nil { cause := errs.Cause(err) switch cause.(type) { case errors.BadParameterError: jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrBadRequest(fmt.Sprintf("Error listing work items: %s", err.Error()))) return ctx.BadRequest(jerrors) default: log.Error(ctx, map[string]interface{}{ "err": err, }, "unable to list the work items") jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrInternal(err.Error())) return ctx.InternalServerError(jerrors) } } response := app.SearchWorkItemList{ Links: &app.PagingLinks{}, Meta: &app.WorkItemListResponseMeta{TotalCount: count}, Data: ConvertWorkItems(ctx.RequestData, result), } setPagingLinks(response.Links, buildAbsoluteURL(ctx.RequestData), len(result), offset, limit, count, "q="+ctx.Q) return ctx.OK(&response) }) }
// Spaces runs the space search action. func (c *SearchController) Spaces(ctx *app.SpacesSearchContext) error { q := ctx.Q if q == "" { return jsonapi.JSONErrorResponse(ctx, goa.ErrBadRequest(fmt.Errorf("Empty search query not allowed"))) } else if q == "*" { q = "" // Allow empty query if * specified } var result []*space.Space var count int var err error offset, limit := computePagingLimts(ctx.PageOffset, ctx.PageLimit) return application.Transactional(c.db, func(appl application.Application) error { var resultCount uint64 result, resultCount, err = appl.Spaces().Search(ctx, &q, &offset, &limit) count = int(resultCount) if err != nil { cause := errs.Cause(err) switch cause.(type) { case errors.BadParameterError: return jsonapi.JSONErrorResponse(ctx, goa.ErrBadRequest(fmt.Sprintf("Error listing spaces: %s", err.Error()))) default: log.Error(ctx, map[string]interface{}{ "query": q, "offset": offset, "limit": limit, "err": err, }, "unable to list spaces") return jsonapi.JSONErrorResponse(ctx, goa.ErrInternal(err.Error())) } } response := app.SearchSpaceList{ Links: &app.PagingLinks{}, Meta: &app.SpaceListMeta{TotalCount: count}, Data: ConvertSpaces(ctx.RequestData, result), } setPagingLinks(response.Links, buildAbsoluteURL(ctx.RequestData), len(result), offset, limit, count, "q="+q) return ctx.OK(&response) }) }
// List runs the list action. func (c *WorkItemLinkTypeController) List(ctx *app.ListWorkItemLinkTypeContext) error { // WorkItemLinkTypeController_List: start_implement return application.Transactional(c.db, func(appl application.Application) error { result, err := appl.WorkItemLinkTypes().List(ctx.Context) if err != nil { jerrors, httpStatusCode := jsonapi.ErrorToJSONAPIErrors(err) return ctx.ResponseData.Service.Send(ctx.Context, httpStatusCode, jerrors) } // Enrich linkCtx := newWorkItemLinkContext(ctx.Context, appl, c.db, ctx.RequestData, ctx.ResponseData, app.WorkItemLinkTypeHref) err = enrichLinkTypeList(linkCtx, result) if err != nil { jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrInternal("Failed to enrich link types: %s", err.Error())) return ctx.InternalServerError(jerrors) } return ctx.OK(result) }) // WorkItemLinkTypeController_List: end_implement }
// Delete runs the delete action. func (c *TrackerqueryController) Delete(ctx *app.DeleteTrackerqueryContext) error { result := application.Transactional(c.db, func(appl application.Application) error { err := appl.TrackerQueries().Delete(ctx.Context, ctx.ID) if err != nil { cause := errs.Cause(err) switch cause.(type) { case remoteworkitem.NotFoundError: jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrNotFound(err.Error())) return ctx.NotFound(jerrors) default: jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrInternal(err.Error())) return ctx.InternalServerError(jerrors) } } return ctx.OK([]byte{}) }) c.scheduler.ScheduleAllQueries() return result }
// Update runs the update action. func (c *WorkItemLinkCategoryController) Update(ctx *app.UpdateWorkItemLinkCategoryContext) error { return application.Transactional(c.db, func(appl application.Application) error { toSave := app.WorkItemLinkCategorySingle{ Data: ctx.Payload.Data, } linkCategory, err := appl.WorkItemLinkCategories().Save(ctx.Context, toSave) if err != nil { jerrors, httpStatusCode := jsonapi.ErrorToJSONAPIErrors(err) return ctx.ResponseData.Service.Send(ctx.Context, httpStatusCode, jerrors) } // Enrich linkCtx := newWorkItemLinkContext(ctx.Context, appl, c.db, ctx.RequestData, ctx.ResponseData, app.WorkItemLinkCategoryHref) err = enrichLinkCategorySingle(linkCtx, linkCategory) if err != nil { jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrInternal("Failed to enrich link category: %s", err.Error())) return ctx.InternalServerError(jerrors) } return ctx.OK(linkCategory) }) }
// Create runs the create action. func (c *TrackerqueryController) Create(ctx *app.CreateTrackerqueryContext) error { result := application.Transactional(c.db, func(appl application.Application) error { tq, err := appl.TrackerQueries().Create(ctx.Context, ctx.Payload.Query, ctx.Payload.Schedule, ctx.Payload.TrackerID) if err != nil { cause := errs.Cause(err) switch cause.(type) { case remoteworkitem.BadParameterError, remoteworkitem.ConversionError: jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrBadRequest(err.Error())) return ctx.BadRequest(jerrors) default: jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrInternal(err.Error())) return ctx.InternalServerError(jerrors) } } ctx.ResponseData.Header().Set("Location", app.TrackerqueryHref(tq.ID)) return ctx.Created(tq) }) c.scheduler.ScheduleAllQueries() return result }
// Show runs the show action. func (c *TrackerController) Show(ctx *app.ShowTrackerContext) error { return application.Transactional(c.db, func(appl application.Application) error { t, err := appl.Trackers().Load(ctx.Context, ctx.ID) if err != nil { cause := errs.Cause(err) switch cause.(type) { case remoteworkitem.NotFoundError: log.Error(ctx, map[string]interface{}{ "trackerID": ctx.ID, }, "tracker controller not found") jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrNotFound(err.Error())) return ctx.NotFound(jerrors) default: jerrors, httpStatusCode := jsonapi.ErrorToJSONAPIErrors(goa.ErrInternal(err.Error())) return ctx.ResponseData.Service.Send(ctx.Context, httpStatusCode, jerrors) } } return ctx.OK(t) }) }
// ErrorHandler turns a Go error into an HTTP response. It should be placed in the middleware chain // below the logger middleware so the logger properly logs the HTTP response. ErrorHandler // understands instances of goa.ServiceError and returns the status and response body embodied in // them, it turns other Go error types into a 500 internal error response. // If verbose is false the details of internal errors is not included in HTTP responses. // If you use github.com/pkg/errors then wrapping the error will allow a trace to be printed to the logs func ErrorHandler(service *goa.Service, verbose bool) goa.Middleware { return func(h goa.Handler) goa.Handler { return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { e := h(ctx, rw, req) if e == nil { return nil } cause := errors.Cause(e) status := http.StatusInternalServerError var respBody interface{} if err, ok := cause.(goa.ServiceError); ok { status = err.ResponseStatus() respBody = err goa.ContextResponse(ctx).ErrorCode = err.Token() rw.Header().Set("Content-Type", goa.ErrorMediaIdentifier) } else { respBody = e.Error() rw.Header().Set("Content-Type", "text/plain") } if status >= 500 && status < 600 { reqID := ctx.Value(reqIDKey) if reqID == nil { reqID = shortID() ctx = context.WithValue(ctx, reqIDKey, reqID) } goa.LogError(ctx, "uncaught error", "err", fmt.Sprintf("%+v", e), "id", reqID, "msg", respBody) if !verbose { rw.Header().Set("Content-Type", goa.ErrorMediaIdentifier) msg := fmt.Sprintf("%s [%s]", http.StatusText(http.StatusInternalServerError), reqID) respBody = goa.ErrInternal(msg) // Preserve the ID of the original error as that's what gets logged, the client // received error ID must match the original if origErrID := goa.ContextResponse(ctx).ErrorCode; origErrID != "" { respBody.(*goa.ErrorResponse).ID = origErrID } } } return service.Send(ctx, status, respBody) } } }
// List runs the list action. func (c *TrackerController) List(ctx *app.ListTrackerContext) error { exp, err := query.Parse(ctx.Filter) if err != nil { jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrBadRequest(fmt.Sprintf("could not parse filter: %s", err.Error()))) return ctx.BadRequest(jerrors) } start, limit, err := parseLimit(ctx.Page) if err != nil { jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrBadRequest(fmt.Sprintf("could not parse paging: %s", err.Error()))) return ctx.BadRequest(jerrors) } return application.Transactional(c.db, func(appl application.Application) error { result, err := appl.Trackers().List(ctx.Context, exp, start, &limit) if err != nil { jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrInternal(fmt.Sprintf("Error listing trackers: %s", err.Error()))) return ctx.InternalServerError(jerrors) } return ctx.OK(result) }) }
// Relations runs the relation action. // TODO: Should only return Resource Identifier Objects, not complete object (See List) func (c *WorkItemCommentsController) Relations(ctx *app.RelationsWorkItemCommentsContext) error { offset, limit := computePagingLimts(ctx.PageOffset, ctx.PageLimit) return application.Transactional(c.db, func(appl application.Application) error { wi, err := appl.WorkItems().Load(ctx, ctx.ID) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrNotFound(err.Error())) } comments, tc, err := appl.Comments().List(ctx, ctx.ID, &offset, &limit) count := int(tc) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrInternal(err.Error())) } _ = wi _ = comments res := &app.CommentRelationshipList{} res.Meta = &app.CommentListMeta{TotalCount: count} res.Data = ConvertCommentsResourceID(ctx.RequestData, comments) res.Links = CreateCommentsRelationLinks(ctx.RequestData, wi) return ctx.OK(res) }) }
// List runs the list action. func (c *WorkItemCommentsController) List(ctx *app.ListWorkItemCommentsContext) error { offset, limit := computePagingLimts(ctx.PageOffset, ctx.PageLimit) return application.Transactional(c.db, func(appl application.Application) error { _, err := appl.WorkItems().Load(ctx, ctx.ID) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrNotFound(err.Error())) } res := &app.CommentList{} res.Data = []*app.Comment{} comments, tc, err := appl.Comments().List(ctx, ctx.ID, &offset, &limit) count := int(tc) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrInternal(err.Error())) } res.Meta = &app.CommentListMeta{TotalCount: count} res.Data = ConvertComments(ctx.RequestData, comments) res.Links = &app.PagingLinks{} setPagingLinks(res.Links, buildAbsoluteURL(ctx.RequestData), len(comments), offset, limit, count) return ctx.OK(res) }) }
// Create runs the create action. func (c *WorkItemCommentsController) Create(ctx *app.CreateWorkItemCommentsContext) error { return application.Transactional(c.db, func(appl application.Application) error { _, err := appl.WorkItems().Load(ctx, ctx.ID) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrNotFound(err.Error())) } currentUser, err := login.ContextIdentity(ctx) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrUnauthorized(err.Error())) } currentUserID, err := uuid.FromString(currentUser) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrUnauthorized(err.Error())) } reqComment := ctx.Payload.Data markup := rendering.NilSafeGetMarkup(reqComment.Attributes.Markup) newComment := comment.Comment{ ParentID: ctx.ID, Body: reqComment.Attributes.Body, Markup: markup, CreatedBy: currentUserID, } err = appl.Comments().Create(ctx, &newComment) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrInternal(err.Error())) } res := &app.CommentSingle{ Data: ConvertComment(ctx.RequestData, &newComment), } return ctx.OK(res) }) }
Ω(rw.ParentHeader["Content-Type"]).Should(Equal([]string{"text/plain"})) Ω(string(rw.Body)).Should(Equal(`"boom"` + "\n")) }) Context("not verbose", func() { BeforeEach(func() { verbose = false }) It("hides the error details", func() { var decoded errorResponse Ω(rw.Status).Should(Equal(500)) Ω(rw.ParentHeader["Content-Type"]).Should(Equal([]string{goa.ErrorMediaIdentifier})) err := service.Decoder.Decode(&decoded, bytes.NewBuffer(rw.Body), "application/json") Ω(err).ShouldNot(HaveOccurred()) msg := goa.ErrInternal(`Internal Server Error [zzz]`).Error() msg = regexp.QuoteMeta(msg) msg = strings.Replace(msg, "zzz", ".+", 1) endIDidx := strings.Index(msg, "]") msg = `\[.*\]` + msg[endIDidx+1:] Ω(fmt.Sprintf("%v", decoded.Error())).Should(MatchRegexp(msg)) }) Context("and goa 500 error", func() { var origID string BeforeEach(func() { verbose = false h = func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { e := goa.ErrInternal("goa-500-boom") origID = e.(goa.ServiceError).Token()