@txstate-mws/graphql-server
Version:
A simple graphql server designed to work with typegraphql.
176 lines (175 loc) • 7.66 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthError = exports.ExecutionError = exports.ParseError = exports.GQLError = exports.UnimplementedError = exports.ValidatedResponse = exports.MutationMessage = exports.MutationMessageType = void 0;
const fastify_txstate_1 = require("fastify-txstate");
const txstate_utils_1 = require("txstate-utils");
const type_graphql_1 = require("type-graphql");
var MutationMessageType;
(function (MutationMessageType) {
MutationMessageType["error"] = "error";
MutationMessageType["warning"] = "warning";
MutationMessageType["success"] = "success";
})(MutationMessageType || (exports.MutationMessageType = MutationMessageType = {}));
(0, type_graphql_1.registerEnumType)(MutationMessageType, {
name: 'MutationMessageType',
valuesConfig: {
error: {
description: 'This error means the mutation cannot and/or did not take place.'
},
warning: {
description: 'The mutation can and/or did complete, but the user should receive the warning anyway (e.g. "Your password sucks but I\'ll allow it.").'
},
success: {
description: 'This message should be shown to the end user before submission to let them know ahead of time that one of their entries passed validation (e.g. username available or password strength high).'
}
}
});
let MutationMessage = class MutationMessage {
constructor(message, arg, type = MutationMessageType.error) {
this.message = message;
this.arg = arg;
this.type = type;
}
};
exports.MutationMessage = MutationMessage;
__decorate([
(0, type_graphql_1.Field)(type => String, { nullable: true, description: 'The path to the arg that produced the error. Dot-separated (lodash.get compatible) if it is deep inside an input type. Null if no particular arg can be blamed for the error.' }),
__metadata("design:type", String)
], MutationMessage.prototype, "arg", void 0);
__decorate([
(0, type_graphql_1.Field)({ description: 'An error message to be shown to the end user, with the context of the given arg.' }),
__metadata("design:type", String)
], MutationMessage.prototype, "message", void 0);
__decorate([
(0, type_graphql_1.Field)(type => MutationMessageType, { description: 'The type of error message. See the enum descriptions for more detail.' }),
__metadata("design:type", String)
], MutationMessage.prototype, "type", void 0);
exports.MutationMessage = MutationMessage = __decorate([
(0, type_graphql_1.ObjectType)(),
__metadata("design:paramtypes", [String, String, String])
], MutationMessage);
let ValidatedResponse = class ValidatedResponse {
constructor(config) {
this.success = !!config?.success;
this.messages = config?.messages ?? [];
}
// push message onto messages array and mark not a success if it's fatal
addMessage(message, arg, type) {
this.messages.push(new MutationMessage(message, arg, type));
if (!type || type === MutationMessageType.error)
this.success = false;
}
// if condition is falsy, error is pushed onto messages list
assert(condition, message, arg) {
if (!condition)
this.addMessage(message, arg);
}
// if input contains arg and is outside allowed range, an error is added to messages
assertBetween(input, arg, min, max) {
const val = (0, txstate_utils_1.get)(input, arg);
if ((0, txstate_utils_1.isNotNull)(val) && (val < min || val > max)) {
this.addMessage(`Value out of range, must be between ${min} and ${max}`, arg);
}
}
assertPositive(input, arg) {
const val = (0, txstate_utils_1.get)(input, arg);
if (val < 0)
this.addMessage('Value must not be negative', arg);
}
assertLength(input, arg, min, max) {
const val = (0, txstate_utils_1.get)(input, arg);
if (val.length > max) {
if (typeof val === 'string')
this.addMessage('Character maximum exceeded.', arg);
else
this.addMessage('Too many entries.', arg);
}
if (val.length < min) {
if (typeof val === 'string')
this.addMessage('Character minimum not met.', arg);
else
this.addMessage('Not enough entries.', arg);
}
}
// do we have errors in our messages array?
hasErrors() {
return this.messages.some(m => m.type === MutationMessageType.error);
}
/**
* SomeSpecificTypeOfResponse.error('OMG fail', 'argName')
*
* Creates a new error response object for service layer to return upon fatal errors.
* (e.g. permission denied, object ID does not exist, other "you shall not pass" types of errors)
*
* @param message Will be first item in the messages array
* @param arg Name of the input argument this message relates to
* @returns ValidatedResponse (whatever class this is being called from)
*/
static error(message, arg) {
return new this({
success: false,
messages: [
new MutationMessage(message, arg, MutationMessageType.error)
]
});
}
};
exports.ValidatedResponse = ValidatedResponse;
__decorate([
(0, type_graphql_1.Field)({ description: 'True if the mutation succeeded (e.g. saved data or passed validation), even if there were warnings.' }),
__metadata("design:type", Boolean)
], ValidatedResponse.prototype, "success", void 0);
__decorate([
(0, type_graphql_1.Field)(type => [MutationMessage]),
__metadata("design:type", Array)
], ValidatedResponse.prototype, "messages", void 0);
exports.ValidatedResponse = ValidatedResponse = __decorate([
(0, type_graphql_1.ObjectType)(),
__metadata("design:paramtypes", [Object])
], ValidatedResponse);
class UnimplementedError extends Error {
constructor() {
super('Requested functionality is not yet implemented.');
}
}
exports.UnimplementedError = UnimplementedError;
class GQLError extends Error {
constructor(message, query, errors) {
super(message);
this.query = query;
this.errors = errors;
}
toString() {
return `${this.message}
${this.errors.map(e => (e.stack ?? '')).join('\n')}
${this.query}`;
}
}
exports.GQLError = GQLError;
class ParseError extends GQLError {
constructor(query, errors) {
super('Failed to parse GraphQL query.', query, errors);
}
}
exports.ParseError = ParseError;
class ExecutionError extends GQLError {
constructor(query, errors) {
super('Error(s) occurred during GraphQL execution.', query, errors);
}
}
exports.ExecutionError = ExecutionError;
class AuthError extends fastify_txstate_1.HttpError {
constructor() {
super(401);
}
}
exports.AuthError = AuthError;