diff --git a/src/IO/LayRequest.js b/src/IO/LayRequest.js index 5de797e..7fa3947 100644 --- a/src/IO/LayRequest.js +++ b/src/IO/LayRequest.js @@ -1,22 +1,49 @@ import RequestParser from '../Utils/RequestParser'; - +/** + * Represents a custom request class that extends the built-in Request class. + * @extends Request + */ export default class LayRequest extends Request { body = undefined; - nativeRequest = null; + #nativeRequest = undefined; + + /** + * Creates an instance of LayRequest. + * @param {*} req - The native request object. + */ constructor(req) { super(req); this.params = new Map(); this.query = new Map(); - this.nativeReq = req; + this.#nativeRequest = req; } - header(key) { - this.headers?.get(key); + /** + * Retrieves the value of a specific header from the request. + * @param {string} key - The header key. + * @returns {string|undefined} The value of the header, or undefined if not found. + */ + header(key, value = undefined) { + if(typeof(value) != 'undefined') { + if(!this.headers) this.headers = new Headers(); + this.headers.set(key, value); + } + return this.headers.get(key); } + /** + * Parses the request body asynchronously. + * @returns {Promise<*>} A promise that resolves to the parsed request body. + */ async parseBody() { - if(typeof this.body != 'undefined') return this.body; - this.body = await RequestParser(this.nativeReq); + if (typeof this.body !== 'undefined') return this.body; + try { + this.body = await RequestParser(this); + return this.body; + } catch (error) { + console.error('Error parsing request body:', error); + throw error; + } } } diff --git a/src/IO/LayResponse.js b/src/IO/LayResponse.js index c687167..afd04b7 100644 --- a/src/IO/LayResponse.js +++ b/src/IO/LayResponse.js @@ -1,43 +1,45 @@ import MimeTypeMap from './MimeTypeMap'; +/** + * Represents a response object for handling HTTP responses. + */ export default class LayResponse { #routing = undefined; #body = 'Not Implemented'; - #file = undefined; - #redirectUrl = undefined; - #statusCode = 502; + #data = undefined; + #statusCode = undefined; #headerList = new Map(); #mimeType = 'text/plain'; - #options = { - disableFileCheck: false, - }; + #options = new Map([ + ['type', 'default'], + ['disableFileCheck', false], + ['layout', undefined], + ]); + getData() { + return this.#data; + } + + getBody() { + return this.#body; + } + + /** + * Creates a new instance of LayResponse. + * @param {Object} routing - The routing object. + */ constructor(routing) { this.#routing = routing; - } - type(key) { - if (!MimeTypeMap.has(key)) { - if(this.#mimeType != 'text/plain') this.#mimeType = MimeTypeMap.get('text'); - return; + if(this.#routing.getServer().hasLayout()) { + this.#options.set('layout', this.#routing.getServer().getLayout()); } - this.mimeType = MimeTypeMap.get(key); - } - - status(value = null) { - if (isNaN(value)) throw new TypeError('status has to be a number'); - this.#statusCode = value || 502; - } - - redirect(value = null, status = 302) { - if (value === null) { - return this.#redirectUrl; - } - - this.#redirectUrl = value; - this.status(status); } + /** + * Returns the headers of the response. + * @returns {Map} The headers map. + */ headers() { if (!this.#headerList.get('Content-Type')) { this.#headerList.set('Content-Type', `${this.#mimeType}`); @@ -45,85 +47,198 @@ export default class LayResponse { return this.#headerList; } - header(key, value) { - this.#headerList.set(key, value); + /** + * Sets or gets the layout for the response. + * @param {string} [layout] - The layout to set. If not provided, returns the current layout. + * @returns {string|LayResponse} The current layout or the LayResponse instance. + */ + layout (layout = undefined) { + if(typeof(layout) == 'undefined') return this.#options.get('layout'); + this.#options.set('layout', layout); + return this; } + /** + * Builds the response based on the specified options. + * @param {Object} [options={}] - The options for building the response. + * @returns {Promise} A promise that resolves to the built response. + */ async build(options = {}) { - if (this.#redirectUrl) { - return Response.redirect(this.#redirectUrl, this.status()); + switch(this.#options.get('type')) { + case 'redirect': + return Response.redirect(this.#body, this.status()); + case 'file': + const file = Bun.file(this.#body); + if(await file.exists()) { + return new Response(file); + } + return undefined; + case 'render': + const loader = await this.#routing.getServer().load(this.#body); + if(!loader) { + return new Response('Not Found', { status: 404 }); + } + console.log(loader); + const { data, content } = await loader.parse(this.#data); + this.#data = data || this.#data; + this.#body = content; + this.type('text'); + break; + default: + case 'default': + break; } - if(this.#file) return new Response(this.#file); - - if(['text/plain'].includes(this.#mimeType)) { - await this.handleLayout(this.#body, options); + if(['text/plain', 'text/html'].includes(this.#mimeType)) { + await this.#handleLayout(this.#body, options); } + if(typeof(this.#statusCode) == 'undefined') this.status(this.#body ? 200 : 500); + return new Response(this.#body, { status: this.status(), headers: this.headers(), }); } - async send() { - await this.text(...arguments); + /** + * Handles the layout rendering for the response. + * @param {string} content - The content to render within the layout. + * @param {Object} [data={}] - The data to pass to the layout. + * @returns {Promise} + * @private + */ + async #handleLayout(content, data = {}) { + if(!this.#options.get('layout')) return - if (typeof(arguments[0]) != 'string' || !arguments[0]) return; - if (this.#options.disableFileCheck) return; - - try { - const temp = Bun.file(arguments[0]); - - if (await temp.exists()) { - this.#file = temp; - } - } catch (err) {} - } - - async text() { - let [value, ...rest] = arguments; - if (typeof value != 'string') { - value = JSON.stringify(value); - } - - this.type('text'); - await this.handleLayout(value, ...rest); - } - - json(data, options = {}) { - let { replacer, space } = options; - if (!Array.isArray(replacer) && typeof replacer != 'function') { - replacer = null; - } - if (isNaN(space) || Math.floor(space) < 0) { - space = 0; - } - this.type('json'); - this.#body = JSON.stringify(data, replacer, space); - } - - async render(path, data = {}) { - const loader = await this.#routing.getServer().load(path); - const content = await loader.parse(data); - - return content; - } - - async handleLayout(content, options = {}) { - this.#body = String(content); - if(!options.hasOwnProperty('layout') || typeof options.layout !== 'string') { - if(this.#routing.getServer().hasLayout()) { - options.layout = this.#routing.getServer().getLayout(); - } - } - - if(!options.layout) return - - const layout = await this.#routing.getServer().load(options.layout); + const layout = await this.#routing.getServer().load(this.#options.get('layout')); + data = { + ...(this.#data || {}), + ...data + }; if(layout) { - this.#body = await layout.parse({ ...(options?.data || {}), content: String(content) }, options); + const rendered = await layout.parse({ content: String(content), ...(data ?? {}) }); + this.#body = rendered.content ?? String(content); this.#mimeType = layout.mimeType; } } + + /** + * Sets a header for the response. + * @param {string} key - The header key. + * @param {string} value - The header value. + * @returns {LayResponse} The LayResponse instance. + */ + header(key, value) { + this.#headerList.set(key, value); + + return this; + } + + /** + * Sets the MIME type of the response based on the provided key. + * @param {string} key - The key to determine the MIME type. + * @returns {LayResponse} The LayResponse instance. + */ + type(key) { + if (!MimeTypeMap.has(key)) { + if(this.#mimeType != 'text/plain') this.#mimeType = MimeTypeMap.get('text'); + return this; + } + this.mimeType = MimeTypeMap.get(key); + + return this; + } + + /** + * Sets or gets the status code of the response. + * @param {number} [value] - The status code to set. If not provided, returns the current status code. + * @returns {number|LayResponse} The current status code or the LayResponse instance. + */ + status(value = undefined) { + if (value == undefined) return this.#statusCode; + if (isNaN(value)) throw new TypeError('status has to be a number'); + this.#statusCode = value || 502; + + return this.#statusCode; + } + + /** + * Sends the response with the provided arguments. + * @returns {LayResponse} The LayResponse instance. + */ + send() { + this.text(...arguments); + return this; + } + + /** + * Sends a file as the response. + * @returns {LayResponse} The LayResponse instance. + */ + sendFile() { + this.#body = arguments[0]; + this.#options.set('type', 'file'); + return this; + } + + /** + * Renders a template with the provided path and data. + * @returns {LayResponse} The LayResponse instance. + */ + render() { + const [ path, data ] = arguments; + this.#body = path; + this.#data = data; + this.#options.set('type', 'render'); + return this; + } + + /** + * Sets the response body as plain text. + * @returns {LayResponse} The LayResponse instance. + */ + text() { + this.#body = arguments[0] ?? ''; + this.#data = arguments[1] ?? {}; + this.#options.set('type', 'default'); + return this; + } + + /** + * Redirects the response to the specified value with the provided status code. + * @param {string} [value=null] - The value to redirect to. If not provided, returns the current redirect value. + * @param {number} [status=302] - The status code for the redirect. + * @returns {number|null} The redirect status code or null if no redirect is set. + */ + redirect(value = null, status = 302) { + if (value === null) { + if(!this.#options.get('type') == 'redirect') return null; + return this.#body + }; + this.#body = value; + this.#options.set('type', 'redirect'); + return this.status(status); + } + + /** + * Sets the response body as JSON. + * @returns {LayResponse} The LayResponse instance. + */ + json() { + const [ data, options ] = arguments; + let { + replacer = null, + space = 0 + } = (options || {}); + + if (!Array.isArray(replacer) && typeof replacer != 'function') replacer = null; + if (isNaN(space) || Math.floor(space) < 0) space = 0; + + this.type('json'); + this.#body = JSON.stringify(data, replacer, space); + this.#data = options; + + return this; + } }