Skip to content

Commit

Permalink
feat(mvc): Improve the Required decorator
Browse files Browse the repository at this point in the history
- Required can be use on property
- add Allow decorator

Closes: #130, #35
  • Loading branch information
Romakita committed Oct 7, 2017
1 parent 547ae0f commit 7eadab5
Show file tree
Hide file tree
Showing 37 changed files with 962 additions and 449 deletions.
12 changes: 10 additions & 2 deletions docs/docs/converters.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ In second place, create a new file in your `converters` folder. Create a new cla
class EventModel {

@JsonProperty()
@Required()
name: string;

@JsonProperty('startDate')
Expand All @@ -53,12 +54,19 @@ class EventModel {
endDate: Date;

@JsonProperty({use: Task})
@Required()
@Allow(null)
tasks: TaskModel[];
}

class TaskModel {
subject: string;
rate: number;
@JsonProperty()
@Required()
subject: string;

@JsonProperty()
@Required()
rate: number;
}
```
> Theses ES6 collections can be used : Map and Set.
Expand Down
23 changes: 0 additions & 23 deletions src/converters/class/PropertyMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,12 @@
import {Storable} from "../../core/class/Storable";
import {NotEnumerable} from "../../core/decorators";
/**
* @module common/converters
*/
/** */
import {IPropertyOptions} from "../interfaces/IPropertyOptions";

export class PropertyMetadata extends Storable implements IPropertyOptions {
/**
*
*/
@NotEnumerable()
protected _required: boolean = false;

constructor(target: any, propertyKey: any) {
super(target, propertyKey);
}

/**
*
* @returns {boolean}
*/
public get required(): boolean {
return this._required;
}

/**
*
* @param value
*/
public set required(value: boolean) {
this._required = value;
}
}
5 changes: 3 additions & 2 deletions src/converters/errors/ConverterSerializationError.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* @module common/converters
*/ /** */
*/
/** */

import {InternalServerError} from "ts-httpexceptions";
import {Type} from "../../core/interfaces/Type";
Expand All @@ -24,6 +25,6 @@ export class ConverterSerializationError extends InternalServerError {
* @returns {string}
*/
static buildMessage(target: Type<any>, err: Error) {
return `Convertion failed for class "${nameOf(target)}".`.trim();
return `Convertion failed for "${nameOf(target)}". ${err.message}`.trim();
}
}
28 changes: 28 additions & 0 deletions src/converters/errors/RequiredPropertyError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @module common/mvc
*/
/** */

import {BadRequest} from "ts-httpexceptions";
import {Type} from "../../core/interfaces";
import {nameOf} from "../../core/utils";

/**
* @private
*/
export class RequiredPropertyError extends BadRequest {

constructor(target: Type<any>, propertyName: string | symbol) {
super(RequiredPropertyError.buildMessage(target, propertyName));
}

/**
*
* @returns {string}
* @param target
* @param propertyName
*/
static buildMessage(target: Type<any>, propertyName: string | symbol) {
return `Property ${propertyName} on class ${nameOf(target)} is required.`;
}
}
28 changes: 28 additions & 0 deletions src/converters/errors/UnknowPropertyError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @module common/mvc
*/
/** */

import {BadRequest} from "ts-httpexceptions";
import {Type} from "../../core/interfaces";
import {nameOf} from "../../core/utils";

/**
* @private
*/
export class UnknowPropertyError extends BadRequest {

constructor(target: Type<any>, propertyName: string) {
super(UnknowPropertyError.buildMessage(target, propertyName));
}

/**
*
* @returns {string}
* @param target
* @param propertyName
*/
static buildMessage(target: Type<any>, propertyName: string) {
return `Property ${propertyName} on class ${nameOf(target)} is not allowed.`;
}
}
4 changes: 3 additions & 1 deletion src/converters/registries/PropertyRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,13 @@ export class PropertyRegistry {
*
* @param target
* @param propertyKey
* @param allowedValues
*/
static required(target: Type<any>, propertyKey: string | symbol) {
static required(target: Type<any>, propertyKey: string | symbol, allowedValues: any[] = []) {
const property = this.get(target, propertyKey);

property.required = true;
property.allowedValues = allowedValues;

this.set(target, propertyKey, property);
this.get(target, propertyKey).store.merge("responses", {
Expand Down
105 changes: 72 additions & 33 deletions src/converters/services/ConverterService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {BadRequest} from "ts-httpexceptions";
import {Metadata} from "../../core/class/Metadata";
import {isArrayOrArrayClass, isEmpty, isPrimitiveOrPrimitiveClass} from "../../core/utils";
import {getClass, isArrayOrArrayClass, isEmpty, isPrimitiveOrPrimitiveClass} from "../../core/utils";
import {InjectorService} from "../../di";
/**
* @module common/converters
Expand All @@ -11,6 +11,8 @@ import {PropertyMetadata} from "../class/PropertyMetadata";
import {CONVERTER} from "../constants/index";
import {ConverterDeserializationError} from "../errors/ConverterDeserializationError";
import {ConverterSerializationError} from "../errors/ConverterSerializationError";
import {RequiredPropertyError} from "../errors/RequiredPropertyError";
import {UnknowPropertyError} from "../errors/UnknowPropertyError";
import {IConverter} from "../interfaces/index";
import {PropertyRegistry} from "../registries/PropertyRegistry";

Expand Down Expand Up @@ -58,7 +60,6 @@ export class ConverterService {
const converter = this.getConverter(obj);

if (converter && converter.serialize) {

// deserialize from a custom JsonConverter
return converter.serialize(obj);
}
Expand All @@ -81,18 +82,35 @@ export class ConverterService {

Object.keys(obj).forEach(propertyKey => {
if (typeof obj[propertyKey] !== "function") {
const propertyMetadata = ConverterService.getPropertyMetadata(properties, propertyKey) || {} as any;
let propertyMetadata = ConverterService.getPropertyMetadata(properties, propertyKey);

if (getClass(obj) !== Object && propertyMetadata === undefined) {
throw new UnknowPropertyError(getClass(obj), propertyKey);
}

propertyMetadata = propertyMetadata || {} as any;
plainObject[propertyMetadata!.name || propertyKey] = this.serialize(obj[propertyKey]);
}
});

plainObject[propertyMetadata.name || propertyKey] = this.serialize(obj[propertyKey]);
// Required validation
properties.forEach((propertyMetadata: PropertyMetadata) => {
const key = propertyMetadata.name || propertyMetadata.propertyKey;
if (!propertyMetadata.isValidValue(plainObject[key])) {
throw new RequiredPropertyError(getClass(obj), propertyMetadata.propertyKey);
}
});

return plainObject;
}

} catch (err) {
/* istanbul ignore next */
throw new ConverterSerializationError(obj, err);
if (err.name === "BAD_REQUEST") {
throw new BadRequest(err.message);
} else {
/* istanbul ignore next */
throw new ConverterSerializationError(getClass(obj), err);
}
}

/* istanbul ignore next */
Expand All @@ -101,8 +119,8 @@ export class ConverterService {

/**
* Convert a plainObject to targetType.
* @param obj
* @param targetType
* @param obj Object source that will be deserialized
* @param targetType Pattern of the object deserialized
* @param baseType
* @returns {any}
*/
Expand Down Expand Up @@ -138,41 +156,23 @@ export class ConverterService {


// Default converter
// if (!isPrimitiveOrPrimitiveClass(obj) && !isPrimitiveOrPrimitiveClass(targetType)) {

const instance = new targetType();
const properties = PropertyRegistry.getProperties(targetType);

Object.keys(obj).forEach((propertyName: string) => {
const propertyMetadata = ConverterService.getPropertyMetadata(properties, propertyName) || {} as any;
const propertyValue = obj[propertyMetadata.name] || obj[propertyName];
const propertyKey = propertyMetadata.propertyKey || propertyName;
try {

if (typeof instance[propertyKey] !== "function") {
instance[propertyKey] = this.deserialize(
propertyValue,
propertyMetadata.isCollection ? propertyMetadata.collectionType : propertyMetadata.type,
propertyMetadata.type
);
}
const propertyMetadata = ConverterService.getPropertyMetadata(properties, propertyName);
return this.convertProperty(obj, instance, propertyName, propertyMetadata);
});

} catch (err) {
/* istanbul ignore next */
(() => {
const castedError = new Error("For " + String(propertyKey) + " with value " + propertyValue + " \n" + err.message);
castedError.stack = err.stack;
throw castedError;
})();
// Required validation
properties.forEach((propertyMetadata: PropertyMetadata) => {
if (!propertyMetadata.isValidValue(instance[propertyMetadata.propertyKey])) {
throw new RequiredPropertyError(targetType, propertyMetadata.propertyKey);
}
});

return instance;

// }

} catch (err) {

/* istanbul ignore next */
if (err.name === "BAD_REQUEST") {
throw new BadRequest(err.message);
Expand All @@ -184,6 +184,45 @@ export class ConverterService {
}
}

/**
*
* @param obj
* @param instance
* @param {string} propertyName
* @param {PropertyMetadata} propertyMetadata
*/
private convertProperty = (obj: any, instance: any, propertyName: string, propertyMetadata?: PropertyMetadata) => {


if (getClass(instance) !== Object && propertyMetadata === undefined) {
throw new UnknowPropertyError(getClass(instance), propertyName);
}

propertyMetadata = propertyMetadata || {} as any;

const propertyValue = obj[propertyMetadata!.name] || obj[propertyName];
const propertyKey = propertyMetadata!.propertyKey || propertyName;

try {

if (typeof instance[propertyKey] !== "function") {
instance[propertyKey] = this.deserialize(
propertyValue,
propertyMetadata!.isCollection ? propertyMetadata!.collectionType : propertyMetadata!.type,
propertyMetadata!.type
);
}

} catch (err) {
/* istanbul ignore next */
(() => {
const castedError = new Error("For " + String(propertyKey) + " with value " + propertyValue + " \n" + err.message);
castedError.stack = err.stack;
throw castedError;
})();
}
};

/**
*
* @param targetType
Expand Down

0 comments on commit 7eadab5

Please sign in to comment.