feat: Typescript for API layer (#817)

Co-authored-by: Philip Korsholm <88927411+pKorsholm@users.noreply.github.com>
Co-authored-by: Zakaria El Asri <33696020+zakariaelas@users.noreply.github.com>
Co-authored-by: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com>
Co-authored-by: Philip Korsholm <philip.korsholm@hotmail.com>
Co-authored-by: Sebastian Rindom <seb@medusa-commerce.com>
This commit is contained in:
Oliver Windall Juhl
2021-11-18 15:19:17 +01:00
committed by GitHub
co-authored by Philip Korsholm Zakaria El Asri Kasper Fabricius Kristensen Philip Korsholm Sebastian Rindom
parent 55e200bf68
commit 373532ecbc
413 changed files with 20957 additions and 10349 deletions
@@ -0,0 +1,2 @@
export const transformDate = ({ value }): Date =>
!isNaN(Date.parse(value)) ? new Date(value) : new Date(Number(value) * 1000)
@@ -0,0 +1,34 @@
import {
registerDecorator,
ValidationArguments,
ValidationOptions,
isDefined,
} from "class-validator"
import { MedusaError } from "medusa-core-utils"
export function IsGreaterThan(
property: string,
validationOptions?: ValidationOptions
) {
return function (object: any, propertyName: string): void {
registerDecorator({
name: "IsGreaterThan",
target: object.constructor,
propertyName: propertyName,
constraints: [property],
options: validationOptions,
validator: {
validate(value: any, args: ValidationArguments) {
const [relatedPropertyName] = args.constraints
const relatedValue = args.object[relatedPropertyName]
return relatedValue ? value > relatedValue : isDefined(value)
},
defaultMessage(args?: ValidationArguments): string {
return `"${propertyName}" must be greater than ${JSON.stringify(
args?.constraints[0]
)}`
},
},
})
}
}
@@ -0,0 +1,120 @@
import {
isArray,
isNumber,
isString,
registerDecorator,
ValidationArguments,
ValidationOptions,
} from "class-validator"
import { isDate } from "lodash"
import { MedusaError } from "medusa-core-utils"
import { validator } from "../validator"
async function typeValidator(
typedClass: any,
plain: unknown
): Promise<boolean> {
switch (typedClass) {
case String:
if (!isString(plain)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`String validation failed: ${plain} is not a string`
)
}
return true
case Number:
if (!isNumber(Number(plain))) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Number validation failed: ${plain} is not a number`
)
}
return true
case Date:
if (!isDate(new Date(plain as string))) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Date validation failed: ${plain} is not a date`
)
}
return true
default:
if (isArray(typedClass) && isArray(plain)) {
const errors: Map<any, string> = new Map()
const result = (
await Promise.all(
(plain as any[]).map(
async (p) =>
await typeValidator(typedClass[0], p).catch((e) => {
errors.set(typedClass[0].name, e.message.split(","))
return false
})
)
)
).some(Boolean)
if (result) {
return true
}
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
Object.fromEntries(errors.entries())
)
}
return (
(await validator(typedClass, plain).then(() => true)) &&
typeof plain === "object"
)
}
}
export function IsType(types: any[], validationOptions?: ValidationOptions) {
// eslint-disable-next-line @typescript-eslint/ban-types
return function (object: Object, propertyName: string): void {
registerDecorator({
name: "IsType",
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
async validate(value: unknown, args: ValidationArguments) {
const errors: Map<any, string> = new Map()
const results = await Promise.all(
types.map(
async (v) =>
await typeValidator(v, value).catch((e) => {
errors.set(v.name, e.message.split(",").filter(Boolean))
return false
})
)
)
if (results.some(Boolean)) {
return true
}
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
JSON.stringify({
message: `${args.property} must be one of: ${types.map(
(t) => `${t.name || (Array.isArray(t) ? t[0]?.name : "")}`
)}`,
details: Object.fromEntries(errors.entries()),
})
)
},
defaultMessage(validationArguments?: ValidationArguments) {
const names = types.map(
(t) => t.name || (isArray(t) ? `${t[0].name}[]` : "")
)
return `${validationArguments?.property} must be one of ${names
.join(", ")
.replace(/, ([^,]*)$/, " or $1")}`
},
},
})
}
}
@@ -0,0 +1,26 @@
import {
registerDecorator,
ValidationArguments,
ValidationOptions,
} from "class-validator"
export function IsISO8601Duration(validationOptions?: ValidationOptions) {
return function (object: any, propertyName: string): void {
registerDecorator({
name: "IsGreaterThan",
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: any, args: ValidationArguments) {
const isoDurationRegex =
/^P(?!$)(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?$/
return isoDurationRegex.test(value)
},
defaultMessage(args?: ValidationArguments): string {
return `"${propertyName}" must be a valid ISO 8601 duration`
},
},
})
}
}