turtle-express
Version:
`turtle-express` is kinda a framework or a library based on `express.js` with an opinionated express router with type safety and schema validation with zod. Also many [more features](https://github.com/mm-ninja-turtles/turtle-express/discussions/7) planni
215 lines • 10.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createHandler = void 0;
const request_validation_1 = require("./request-validation");
const resolver_validation_1 = require("./resolver-validation");
function createHandler(options) {
const resolverType = options.resolverType ?? 'controlled';
const _handlerFunc = async (req, res, next) => {
const handlerContext = {
requestValidation: {
params: null,
query: null,
body: null,
},
resolverValidation: null,
resolver: null,
response: {
_failedReason: '',
success: true,
status: 200,
message: 'Success.',
data: null,
error: null,
},
};
const state = {
previous: 'idle',
current: 'idle',
};
Promise.resolve()
.then(async () => {
// STATE: 1 - idle
if (state.current === 'idle') {
state.previous = state.current; // idle
state.current = 'request_validation';
}
// STATE: 2 - request_validation
if (state.current === 'request_validation') {
if (state.previous !== 'idle')
throw new Error('[case `request_validation`]: Invalid state');
// if request schema is provided
if (options.request) {
// run request validation
const validation = (0, request_validation_1.requestValidation)(req, options.request);
// store validation result in context
handlerContext.requestValidation = validation.context;
// if at least one validation failed
if (validation.ifOneValidationFailed()) {
// prepare response and response back with validation error
state.previous = state.current; // request_validation
state.current = 'before_response';
}
// if no validation failed
else {
// continue to resolver function
state.previous = state.current; // request_validation
state.current = 'resolver';
}
}
// if request schema is not provided
else {
// skip to resolver function
state.previous = state.current; // request_validation
state.current = 'resolver';
}
}
// STATE: 3 - resolver
if (state.current === 'resolver') {
if (state.previous !== 'request_validation')
throw new Error('[case `resolver`]: Invalid state');
// get request validations from context
const { params, query, body } = handlerContext.requestValidation;
// construct resolver function context object
const ctx = {
params: params?.success ? params.data : null,
query: query?.success ? query.data : null,
body: body?.success ? body.data : null,
};
// execute the resolver function and store the result in context
handlerContext.resolver = await options.resolver(resolverType === 'controlled'
? { ctx, req } // only provide context and request if resolver is controlled
: { ctx, req, res });
state.previous = state.current; // resolver
// go to resolver_validation state if resolver is controlled
if (resolverType === 'controlled')
state.current = 'resolver_validation';
// skip to final if resolver is not controlled
else
state.current = 'final';
}
// STATE: 4 - resolver_validation
if (state.current === 'resolver_validation') {
if (state.previous !== 'resolver')
throw new Error('[case `resolver_validation`]: Invalid state');
// validate resolver result with response schema and store in context
handlerContext.resolverValidation = (0, resolver_validation_1.resolverValidation)(handlerContext.resolver, options.response);
// go to before_response to prepare response object
state.previous = state.current; // resolver_validation
state.current = 'before_response';
}
// STATE: 5 - before_response
if (state.current === 'before_response') {
// STATE: 5.1 - if previous state is request_validation
if (state.previous === 'request_validation') {
// request validation might have failed
// get request validation from context
const { params, query, body } = handlerContext.requestValidation;
// construct error object
const error = {
params: params?.success === true ? null : params?.error.format(),
query: query?.success === true ? null : query?.error.format(),
body: body?.success === true ? null : body?.error.format(),
};
// set error response to context
handlerContext.response = {
_failedReason: 'request_validation',
status: 400,
success: false,
message: 'Validation Failed.',
data: null,
error,
};
}
// STATE: 5.2 - if previous state is resolver_validation
else if (state.previous === 'resolver_validation') {
// get resolver result
const resolverResult = handlerContext.resolver;
// get resolver validation result
const resolverValidation = handlerContext.resolverValidation;
// get the http status code to response
// by getting the first result from the resolver result
// which is the result of the resolver execution
let status = Object.keys(resolverResult)[0];
// set the response message default to Success.
let message = 'Success.';
// if http status is not in error range
// and resolver validation failed,
// validation failed
if (status < 400 && resolverValidation?.success === false) {
// set status to 400 with validation failed message.
status = 400;
message = 'Response Validation Failed.';
}
// if http status is in error range
// and resolver validation is success,
// resolver failed response
else if (status >= 400 && resolverValidation?.success === true) {
// change message to Failed.
message = 'Failed.';
}
// construct response object and store in context
handlerContext.response = {
_failedReason: 'resolver_validation',
status,
success: resolverValidation?.success ?? false,
message,
data: resolverValidation?.success === true
? resolverValidation.data
: null,
error: resolverValidation?.success === true
? null
: resolverValidation?.error.format(),
};
}
// STATE: 5.3 - otherwise, throw error, something might have gone wrong
else
throw new Error('[case `before_response`]: Invalid state');
// go to final state
state.previous = state.current; // before_response
state.current = 'final';
}
// STATE: 6 - final
if (state.current === 'final') {
// STATE: 6.1 - if previous state is from resolver
if (state.previous === 'resolver') {
// resolver is uncontrolled type
// just return the resolver result
return handlerContext.resolver;
}
// STATE: 6.2 - if previous state is from before_response
else if (state.previous === 'before_response') {
// get response from context
const response = handlerContext.response;
const status = Number(response.status);
// construct response body with context
const body = {
success: response.success,
message: response.message,
data: response.data,
error: response.error,
};
// response with status and body
return res.status(status).send(body);
}
// STATE: 6.3 - otherwise, throw error something might have wrong
else
throw new Error('[case `final`]: Invalid state');
}
})
.catch(next);
};
return {
_id: options.id,
_middlewares: options.middlewares ?? [],
_getOpenApiSchemas() {
return options;
},
_getOpenApiPathItem() {
return options;
},
_handlerFunc,
};
}
exports.createHandler = createHandler;
//# sourceMappingURL=create-handler.js.map