Skip to content

Commit

Permalink
Merge pull request #6 from SeanLuis/feature/adding_accesors_utilities
Browse files Browse the repository at this point in the history
feature: adding accessors (getters & setters) decorators utilities and documentation
  • Loading branch information
SeanLuis authored Mar 12, 2024
2 parents 8659d2f + e5bcd4b commit 1623044
Show file tree
Hide file tree
Showing 13 changed files with 268 additions and 4 deletions.
75 changes: 72 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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:
Expand Down
41 changes: 41 additions & 0 deletions src/decorators/accessors/Accessors.ts
Original file line number Diff line number Diff line change
@@ -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 <T extends { new (...args: any[]): {} }>(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,
});
}
});
};
}
19 changes: 19 additions & 0 deletions src/decorators/accessors/Getter.ts
Original file line number Diff line number Diff line change
@@ -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,
});
};
}
19 changes: 19 additions & 0 deletions src/decorators/accessors/Setter.ts
Original file line number Diff line number Diff line change
@@ -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,
});
};
}
3 changes: 3 additions & 0 deletions src/decorators/accessors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { Accessors } from './Accessors';
export { Getter } from './Getter';
export { Setter } from './Setter';
1 change: 1 addition & 0 deletions src/decorators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
5 changes: 4 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export {
validateCustom,
validateChain,
validateContextual,
validateNested,
validateNested,
validateEmail,
validatePassword,
} from './validators';
Expand All @@ -45,6 +45,9 @@ export {
Contextual,
Email,
Password,
Accessors,
Getter,
Setter
} from './decorators';


Expand Down
18 changes: 18 additions & 0 deletions src/interfaces/accessors/IAccessors.ts
Original file line number Diff line number Diff line change
@@ -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;
}
10 changes: 10 additions & 0 deletions src/interfaces/accessors/IGetter.ts
Original file line number Diff line number Diff line change
@@ -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;
}
10 changes: 10 additions & 0 deletions src/interfaces/accessors/ISetter.ts
Original file line number Diff line number Diff line change
@@ -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;
}
31 changes: 31 additions & 0 deletions tests/decorators/accessors/Accessor.test.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});

19 changes: 19 additions & 0 deletions tests/decorators/accessors/Getter.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
21 changes: 21 additions & 0 deletions tests/decorators/accessors/Setter.test.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});

0 comments on commit 1623044

Please sign in to comment.