mirror of
https://github.com/sartography/cr-connect-workflow.git
synced 2025-02-22 12:48:25 +00:00
Most noteable is the addition of the line on which the error occurs for script tasks. It will report the line number and pass back the content of the line that failed. The validator only returns the first error it encounters, as it's clear that all we ever get right now is two of the same error. Did a lot of work between this and spiffworkflow to remove all the places where we obfuscate or drop details as we converted between workflowExceptions and APIExceptions. Dropped the python levenshtein dependency, in favor of just rolling a simple one ourselves in Spiffworkflow.
100 lines
4.2 KiB
Python
100 lines
4.2 KiB
Python
from SpiffWorkflow import WorkflowException
|
|
from SpiffWorkflow.exceptions import WorkflowTaskExecException
|
|
from flask import g
|
|
from werkzeug.exceptions import InternalServerError
|
|
|
|
from crc import ma, app
|
|
|
|
import sentry_sdk
|
|
|
|
|
|
class ApiError(Exception):
|
|
def __init__(self, code, message, status_code=400,
|
|
file_name="", task_id="", task_name="", tag="", task_data=None, error_type="", line_number=0, offset=0):
|
|
if task_data is None:
|
|
task_data = {}
|
|
self.status_code = status_code
|
|
self.code = code # a short consistent string describing the error.
|
|
self.message = message # A detailed message that provides more information.
|
|
self.task_id = task_id or "" # OPTIONAL: The id of the task in the BPMN Diagram.
|
|
self.task_name = task_name or "" # OPTIONAL: The name of the task in the BPMN Diagram.
|
|
self.file_name = file_name or "" # OPTIONAL: The file that caused the error.
|
|
self.tag = tag or "" # OPTIONAL: The XML Tag that caused the issue.
|
|
self.task_data = task_data or "" # OPTIONAL: A snapshot of data connected to the task when error occurred.
|
|
self.line_number = line_number
|
|
self.offset = offset
|
|
self.error_type = error_type
|
|
if hasattr(g, 'user'):
|
|
user = g.user.uid
|
|
else:
|
|
user = 'Unknown'
|
|
self.task_user = user
|
|
# This is for sentry logging into Slack
|
|
sentry_sdk.set_context("User", {'user': user})
|
|
Exception.__init__(self, self.message)
|
|
|
|
@classmethod
|
|
def from_task(cls, code, message, task, status_code=400, line_number=0, offset=0, error_type="", error_line=""):
|
|
"""Constructs an API Error with details pulled from the current task."""
|
|
instance = cls(code, message, status_code=status_code)
|
|
instance.task_id = task.task_spec.name or ""
|
|
instance.task_name = task.task_spec.description or ""
|
|
instance.file_name = task.workflow.spec.file or ""
|
|
instance.line_number = line_number
|
|
instance.offset = offset
|
|
instance.error_type = error_type
|
|
instance.error_line = error_line
|
|
|
|
# Fixme: spiffworkflow is doing something weird where task ends up referenced in the data in some cases.
|
|
if "task" in task.data:
|
|
task.data.pop("task")
|
|
|
|
instance.task_data = task.data
|
|
app.logger.error(message, exc_info=True)
|
|
return instance
|
|
|
|
@classmethod
|
|
def from_task_spec(cls, code, message, task_spec, status_code=400):
|
|
"""Constructs an API Error with details pulled from the current task."""
|
|
instance = cls(code, message, status_code=status_code)
|
|
instance.task_id = task_spec.name or ""
|
|
instance.task_name = task_spec.description or ""
|
|
if task_spec._wf_spec:
|
|
instance.file_name = task_spec._wf_spec.file
|
|
app.logger.error(message, exc_info=True)
|
|
return instance
|
|
|
|
@classmethod
|
|
def from_workflow_exception(cls, code, message, exp: WorkflowException):
|
|
"""We catch a lot of workflow exception errors,
|
|
so consolidating the code, and doing the best things
|
|
we can with the data we have."""
|
|
if isinstance(exp, WorkflowTaskExecException):
|
|
return ApiError.from_task(code, message, exp.task, line_number=exp.line_number,
|
|
offset=exp.offset,
|
|
error_type=exp.exception.__class__.__name__,
|
|
error_line=exp.error_line)
|
|
|
|
else:
|
|
return ApiError.from_task_spec(code, message, exp.sender)
|
|
|
|
|
|
class ApiErrorSchema(ma.Schema):
|
|
class Meta:
|
|
fields = ("code", "message", "workflow_name", "file_name", "task_name", "task_id",
|
|
"task_data", "task_user", "hint", "line_number", "offset", "error_type", "error_line")
|
|
|
|
|
|
@app.errorhandler(ApiError)
|
|
def handle_invalid_usage(error):
|
|
response = ApiErrorSchema().dump(error)
|
|
return response, error.status_code
|
|
|
|
|
|
@app.errorhandler(InternalServerError)
|
|
def handle_internal_server_error(e):
|
|
original = getattr(e, "original_exception", None)
|
|
api_error = ApiError(code='Internal Server Error (500)', message=str(original))
|
|
response = ApiErrorSchema().dump(api_error)
|
|
return response, 500
|