From e5bcd4b8c3840b2b99baa658bfadb5ec014265b9 Mon Sep 17 00:00:00 2001 From: SeanLuis Date: Tue, 12 Mar 2024 15:49:47 -0300 Subject: [PATCH] feature: adding accessors (getters & setters) decorators utilities and documentation --- README.md | 75 ++++++++++++++++++++- src/decorators/accessors/Accessors.ts | 41 +++++++++++ src/decorators/accessors/Getter.ts | 19 ++++++ src/decorators/accessors/Setter.ts | 19 ++++++ src/decorators/accessors/index.ts | 3 + src/decorators/index.ts | 1 + src/index.ts | 5 +- src/interfaces/accessors/IAccessors.ts | 18 +++++ src/interfaces/accessors/IGetter.ts | 10 +++ src/interfaces/accessors/ISetter.ts | 10 +++ tests/decorators/accessors/Accessor.test.ts | 31 +++++++++ tests/decorators/accessors/Getter.test.ts | 19 ++++++ tests/decorators/accessors/Setter.test.ts | 21 ++++++ 13 files changed, 268 insertions(+), 4 deletions(-) create mode 100644 src/decorators/accessors/Accessors.ts create mode 100644 src/decorators/accessors/Getter.ts create mode 100644 src/decorators/accessors/Setter.ts create mode 100644 src/decorators/accessors/index.ts create mode 100644 src/interfaces/accessors/IAccessors.ts create mode 100644 src/interfaces/accessors/IGetter.ts create mode 100644 src/interfaces/accessors/ISetter.ts create mode 100644 tests/decorators/accessors/Accessor.test.ts create mode 100644 tests/decorators/accessors/Getter.test.ts create mode 100644 tests/decorators/accessors/Setter.test.ts diff --git a/README.md b/README.md index 09556b5..574c784 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ REST Data Validator is a versatile library designed to offer comprehensive valid - [Using Decorators for Validation](#using-decorators-for-validation) - [Custom Validation Rules](#custom-validation-rules) - [Validators and Decorators](#validators-and-decorators) - - [ClassValidator Decorator](#classvalidator-decorator) + - [Class Decorator](#classvalidator-decorator) - [Validation Decorators](#validation-decorators) - [String Decorator](#string-decorator) - [Number Decorator](#number-decorator) @@ -42,10 +42,14 @@ REST Data Validator is a versatile library designed to offer comprehensive valid - [Nested Decorator](#nested-decorator) - [Contextual Decorator](#contextual-decorator) - [Sanitizer Functions](#sanitizer-functions) - - [Validation Utilities](#validation-utilities) + - Validation Utilities - [Async Validators](#async-validators) - [Nested Validators](#nested-validators) - [Contextual Validators](#contextual-validators) + - Decorators Utility + - [Accessors Decorator](#accessors-decorator) + - [Getter Decorator](#getter-decorator) + - [Setter Decorator](#setter-decorator) - [Roadmap](#roadmap) - [Contributing](#contributing) - [Support Us](#support-us) @@ -157,7 +161,7 @@ const result = validateCustom("user123", customUsernameValidator); console.log(result); ``` -# ClassValidator Decorator +# Class Decorator The `ClassValidator` decorator is used at the class level to enable validation of its properties using the decorators provided by the REST Data Validator library. This decorator is essential for activating and applying the defined property validators within a class. @@ -1017,6 +1021,71 @@ In healthcare applications, validate patient records contextually based on admis The Contextual Validator, with its dynamic and versatile nature, is ideal for any application that requires contextual awareness in its validation logic, ensuring data integrity and adherence to business rules and standards. +# Accessors Decorator + +The `Accessors` decorator is a convenient way to automatically create getters and setters for class properties. This simplifies the encapsulation of properties and promotes best practices with minimal boilerplate code. + +## Usage + +```typescript +import { Accessors } from "rest-data-validator"; + +@Accessors({ includePrivate: true }) +class Example { + private _property: string; + + constructor(property: string) { + this._property = property; + } +} +``` + +### Options + +- `includePrivate`: Include private properties in accessor generation (default is `false`). +- `enumerable`: Mark properties as enumerable (default is `false`). +- `writable`: Mark properties as writable (default is `true`). + +# Getter Decorator + +The `Getter` decorator simplifies the creation of a getter for a specific property, making it read-only by default but visible during property enumeration if specified. + +## Usage + +```typescript +import { Getter } from "rest-data-validator"; + +class Example { + @Getter({ enumerable: true }) + private _property: string = 'default'; +} +``` + +### Options + +- `enumerable`: Make the getter enumerable (default is `true`). + +# Setter Decorator + +The `Setter` decorator allows for the automatic creation of a setter for a specific property, giving you the ability to control the writability of a property dynamically. + +## Usage + +```typescript +import { Setter } from "rest-data-validator"; + +class Example { + @Setter({ writable: true }) + private _property: string = 'default'; +} +``` + +### Options + +- `writable`: Make the setter writable (default is `true`). + +These decorators and interfaces form part of the `rest-data-validator`'s effort to streamline the property management within classes, focusing on clean, maintainable, and efficient code. + ## Roadmap The `rest-data-validator` project aims to continually evolve with the needs of developers and the dynamics of RESTful API design. Below is a tentative roadmap of features and improvements we're exploring: diff --git a/src/decorators/accessors/Accessors.ts b/src/decorators/accessors/Accessors.ts new file mode 100644 index 0000000..4acfdc0 --- /dev/null +++ b/src/decorators/accessors/Accessors.ts @@ -0,0 +1,41 @@ +import { IAccessors } from "../../interfaces/accessors/IAccessors"; + +/** + * Decorator function that automatically creates getters and setters for properties. + * @param options - Optional. The options for the Accessors decorator. + * @returns A decorator function. + */ +export function Accessors(options?: IAccessors) { + return function (constructor: T) { + const instance = new constructor(); + + const instanceProperties = Object.getOwnPropertyNames(instance); + + instanceProperties.forEach(propertyName => { + if (propertyName === 'constructor' || (!options?.includePrivate && propertyName.startsWith('_'))) { + return; + } + + const baseName = propertyName.startsWith('_') ? propertyName.slice(1) : propertyName; + const capitalizedBaseName = baseName.charAt(0).toUpperCase() + baseName.slice(1); + + const getterName = `get${capitalizedBaseName}`; + if (typeof constructor.prototype[getterName] === 'undefined') { + Object.defineProperty(constructor.prototype, getterName, { + get() { return this[propertyName]; }, + enumerable: options?.enumerable ?? false, + configurable: true, + }); + } + + const setterName = `set${capitalizedBaseName}`; + if (typeof constructor.prototype[setterName] === 'undefined') { + Object.defineProperty(constructor.prototype, setterName, { + set(newValue) { this[propertyName] = newValue; }, + enumerable: options?.enumerable ?? false, + configurable: true, + }); + } + }); + }; +} diff --git a/src/decorators/accessors/Getter.ts b/src/decorators/accessors/Getter.ts new file mode 100644 index 0000000..50584ee --- /dev/null +++ b/src/decorators/accessors/Getter.ts @@ -0,0 +1,19 @@ +import { IGetter } from "../../interfaces/accessors/IGetter"; + +/** + * Decorator function that automatically creates a getter for a property. + * @param options - Optional. The options for the Getter decorator. + * @returns A decorator function. + */ +export function Getter(options?: IGetter) { + return function (target: any, propertyName: string) { + const baseName = propertyName.startsWith('_') ? propertyName.slice(1) : propertyName; + const capitalizedBaseName = baseName.charAt(0).toUpperCase() + baseName.slice(1); + + Object.defineProperty(target, `get${capitalizedBaseName}`, { + get() { return this[propertyName]; }, + enumerable: options?.enumerable ?? true, + configurable: true, + }); + }; +} \ No newline at end of file diff --git a/src/decorators/accessors/Setter.ts b/src/decorators/accessors/Setter.ts new file mode 100644 index 0000000..af59bce --- /dev/null +++ b/src/decorators/accessors/Setter.ts @@ -0,0 +1,19 @@ +import { ISetter } from "../../interfaces/accessors/ISetter"; + +/** + * Decorator function that automatically creates a setter for a property. + * @param options - Optional. The options for the Setter decorator. + * @returns A decorator function. + */ +export function Setter(options?: ISetter) { + return function (target: any, propertyName: string) { + const baseName = propertyName.startsWith('_') ? propertyName.slice(1) : propertyName; + const capitalizedBaseName = baseName.charAt(0).toUpperCase() + baseName.slice(1); + + Object.defineProperty(target, `set${capitalizedBaseName}`, { + set(newValue) { this[propertyName] = newValue; }, + enumerable: options?.writable ?? true, + configurable: true, + }); + }; +} diff --git a/src/decorators/accessors/index.ts b/src/decorators/accessors/index.ts new file mode 100644 index 0000000..fdb5f0f --- /dev/null +++ b/src/decorators/accessors/index.ts @@ -0,0 +1,3 @@ +export { Accessors } from './Accessors'; +export { Getter } from './Getter'; +export { Setter } from './Setter'; \ No newline at end of file diff --git a/src/decorators/index.ts b/src/decorators/index.ts index 8b33c35..f7199a7 100644 --- a/src/decorators/index.ts +++ b/src/decorators/index.ts @@ -13,4 +13,5 @@ export { Required } from './RequiredValidator'; export { String } from './StringValidator'; export { Nested } from './NestedValidator'; export { Contextual } from './ContextualValidator'; +export { Accessors, Getter, Setter } from './accessors'; export { Password } from './PasswordValidator'; diff --git a/src/index.ts b/src/index.ts index f06ff80..1500a5f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,7 +19,7 @@ export { validateCustom, validateChain, validateContextual, - validateNested, + validateNested, validateEmail, validatePassword, } from './validators'; @@ -45,6 +45,9 @@ export { Contextual, Email, Password, + Accessors, + Getter, + Setter } from './decorators'; diff --git a/src/interfaces/accessors/IAccessors.ts b/src/interfaces/accessors/IAccessors.ts new file mode 100644 index 0000000..d221940 --- /dev/null +++ b/src/interfaces/accessors/IAccessors.ts @@ -0,0 +1,18 @@ +/** + * `IAccessors` interface for AutoAccessors decorator options. + * + * @property {boolean} includePrivate - Optional. Determines if the private properties should be included in the accessor. + * If set to true, private properties are included. + * If set to false or omitted, private properties are not included. + * @property {boolean} enumerable - Optional. Determines if the property shows up during enumeration of the properties on the corresponding object. + * If set to true, the property appears during enumeration. + * If set to false or omitted, the property does not appear. + * @property {boolean} writable - Optional. Determines if the property should be writable. + * If set to true, the property can be changed. + * If set to false or omitted, the property is read-only. + */ +export interface IAccessors { + includePrivate?: boolean; + enumerable?: boolean; + writable?: boolean; +} \ No newline at end of file diff --git a/src/interfaces/accessors/IGetter.ts b/src/interfaces/accessors/IGetter.ts new file mode 100644 index 0000000..25f22d7 --- /dev/null +++ b/src/interfaces/accessors/IGetter.ts @@ -0,0 +1,10 @@ +/** + * `IGetter` interface for AutoGetter decorator options. + * + * @property {boolean} enumerable - Optional. Determines if the property shows up during enumeration of the properties on the corresponding object. + * If set to true, the property appears during enumeration. + * If set to false or omitted, the property does not appear. + */ +export interface IGetter { + enumerable?: boolean; +} \ No newline at end of file diff --git a/src/interfaces/accessors/ISetter.ts b/src/interfaces/accessors/ISetter.ts new file mode 100644 index 0000000..d9c1698 --- /dev/null +++ b/src/interfaces/accessors/ISetter.ts @@ -0,0 +1,10 @@ +/** + * `ISetter` interface for AutoSetter decorator options. + * + * @property {boolean} writable - Optional. Determines if the property should be writable. + * If set to true, the property can be changed. + * If set to false or omitted, the property is read-only. + */ +export interface ISetter { + writable?: boolean; +} \ No newline at end of file diff --git a/tests/decorators/accessors/Accessor.test.ts b/tests/decorators/accessors/Accessor.test.ts new file mode 100644 index 0000000..da1b9fc --- /dev/null +++ b/tests/decorators/accessors/Accessor.test.ts @@ -0,0 +1,31 @@ +// @ts-nocheck + +import { Accessors } from '../../../src'; + +interface AccessorsMethods { + getProp(): string; + setProp(value: string): void; +} + +@Accessors({ includePrivate: true }) +class TestClass { + private _prop: string = 'test'; + + constructor(prop: string) { + this._prop = prop; + } +} + +describe('AutoAccessors', () => { + it('should create a setter for the property', () => { + const testInstance = new TestClass('initial') as TestClass & AccessorsMethods; + + expect(testInstance).toHaveProperty('setProp'); + expect(testInstance).toHaveProperty('getProp'); + expect(testInstance.getProp).toBeDefined(); + + testInstance. _prop = 'new value'; + expect(testInstance._prop).toBe('new value'); + }); +}); + diff --git a/tests/decorators/accessors/Getter.test.ts b/tests/decorators/accessors/Getter.test.ts new file mode 100644 index 0000000..562512f --- /dev/null +++ b/tests/decorators/accessors/Getter.test.ts @@ -0,0 +1,19 @@ +// @ts-nocheck + +import { Getter } from '../../../src'; + +interface GetterMethod { + getProp(): string; +} + +class TestClass { + @Getter() private _prop: string = 'test'; +} + +describe('AutoGetter', () => { + it('should create a getter for the property', () => { + const testInstance = new TestClass() as TestClass & GetterMethod; + expect(testInstance).toHaveProperty('getProp'); + expect(testInstance.getProp).toBeDefined(); + }); +}); diff --git a/tests/decorators/accessors/Setter.test.ts b/tests/decorators/accessors/Setter.test.ts new file mode 100644 index 0000000..ccce632 --- /dev/null +++ b/tests/decorators/accessors/Setter.test.ts @@ -0,0 +1,21 @@ +// @ts-nocheck + +import { Setter } from '../../../src'; + +interface SetterMethod { + setProp(value: string): void; +} + +class TestClass { + @Setter() private _prop: string = 'test'; +} + +describe('AutoSetter', () => { + it('should create a setter for the property', () => { + const testInstance = new TestClass() as TestClass & SetterMethod; + expect(testInstance).toHaveProperty('setProp'); + + testInstance._prop = 'new value'; + expect(testInstance._prop).toBe('new value'); + }); +}); \ No newline at end of file