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:
co-authored by
Philip Korsholm
Zakaria El Asri
Kasper Fabricius Kristensen
Philip Korsholm
Sebastian Rindom
parent
55e200bf68
commit
373532ecbc
@@ -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`
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user