chore(): Reorganize modules (#7210)

**What**
Move all modules to the modules directory
This commit is contained in:
Adrien de Peretti
2024-05-02 17:33:34 +02:00
committed by GitHub
parent 7a351eef09
commit 4eae25e1ef
870 changed files with 91 additions and 62 deletions

View File

@@ -0,0 +1,249 @@
import { RuleOperator } from "@medusajs/utils"
import { isContextValid } from "../utils"
describe("isContextValidForRules", () => {
const context = {
attribute1: "value1",
attribute2: "value2",
attribute3: "value3",
}
const validRule = {
attribute: "attribute1",
operator: RuleOperator.EQ,
value: "value1",
}
const invalidRule = {
attribute: "attribute2",
operator: RuleOperator.EQ,
value: "wrongValue",
}
it("returns true when all rules are valid", () => {
const rules = [validRule, validRule]
expect(isContextValid(context, rules)).toBe(true)
})
it("returns true when some rules are valid", () => {
const rules = [validRule, validRule]
const options = { someAreValid: true }
expect(isContextValid(context, rules, options)).toBe(true)
})
it("returns true when some rules are valid and someAreValid is true", () => {
const rules = [validRule, invalidRule]
const options = { someAreValid: true }
expect(isContextValid(context, rules, options)).toBe(true)
})
it("returns false when some rules are valid", () => {
const rules = [validRule, invalidRule]
expect(isContextValid(context, rules)).toBe(false)
})
it("returns false when no rules are valid and someAreValid is true", () => {
const rules = [invalidRule, invalidRule]
const options = { someAreValid: true }
expect(isContextValid(context, rules, options)).toBe(false)
})
it("returns false when no rules are valid", () => {
const rules = [invalidRule, invalidRule]
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'gt' operator is valid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.GT,
value: "1", // 2 > 1
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'gt' operator is invalid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.GT,
value: "0", // 0 > 0
},
]
const context = { attribute1: "0" }
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'gte' operator is valid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.GTE,
value: "2", // 2 >= 2
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'gte' operator is invalid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.GTE,
value: "3", // 2 >= 3
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'lt' operator is valid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.LT,
value: "3", // 2 < 3
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'lt' operator is invalid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.LT,
value: "2", // 2 < 2
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'lte' operator is valid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.LTE,
value: "2", // 2 <= 2
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(true)
})
// ... existing tests ...
it("returns false when the 'lte' operator is invalid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.LTE,
value: "1", // 2 <= 1
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'in' operator is valid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.IN,
value: ["1", "2", "3"], // 2 in [1, 2, 3]
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'in' operator is invalid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.IN,
value: ["1", "3", "4"], // 2 in [1, 3, 4]
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'nin' operator is valid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.NIN,
value: ["1", "3", "4"], // 2 not in [1, 3, 4]
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'nin' operator is invalid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.NIN,
value: ["1", "2", "3"], // 2 not in [1, 2, 3]
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'ne' operator is valid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.NE,
value: "1", // 2 != 1
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'ne' operator is invalid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.NE,
value: "2", // 2 != 2
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'eq' operator is valid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.EQ,
value: "2", // 2 == 2
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'eq' operator is invalid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.EQ,
value: "1", // 2 == 1
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(false)
})
})

View File

@@ -0,0 +1 @@
export * from './utils'

View File

@@ -0,0 +1,172 @@
import {
isObject,
isString,
MedusaError,
pickValueFromObject,
RuleOperator,
} from "@medusajs/utils"
/**
* The rule engine here is kept inside the module as of now, but it could be moved
* to the utils package and be used across the different modules that provides context
* based rule filtering.
*
* TODO: discussion around that should happen at some point
*/
export type Rule = {
attribute: string
operator: Lowercase<keyof typeof RuleOperator>
value: string | string[] | null
}
export const availableOperators = Object.values(RuleOperator)
const isDate = (str: string) => {
return !isNaN(Date.parse(str))
}
const operatorsPredicate = {
in: (contextValue: string, ruleValue: string[]) =>
ruleValue.includes(contextValue),
nin: (contextValue: string, ruleValue: string[]) =>
!ruleValue.includes(contextValue),
eq: (contextValue: string, ruleValue: string) => contextValue === ruleValue,
ne: (contextValue: string, ruleValue: string) => contextValue !== ruleValue,
gt: (contextValue: string, ruleValue: string) => {
if (isDate(contextValue) && isDate(ruleValue)) {
return new Date(contextValue) > new Date(ruleValue)
}
return Number(contextValue) > Number(ruleValue)
},
gte: (contextValue: string, ruleValue: string) => {
if (isDate(contextValue) && isDate(ruleValue)) {
return new Date(contextValue) >= new Date(ruleValue)
}
return Number(contextValue) >= Number(ruleValue)
},
lt: (contextValue: string, ruleValue: string) => {
if (isDate(contextValue) && isDate(ruleValue)) {
return new Date(contextValue) < new Date(ruleValue)
}
return Number(contextValue) < Number(ruleValue)
},
lte: (contextValue: string, ruleValue: string) => {
if (isDate(contextValue) && isDate(ruleValue)) {
return new Date(contextValue) <= new Date(ruleValue)
}
return Number(contextValue) <= Number(ruleValue)
},
}
/**
* Validate contextValue context object from contextValue set of rules.
* By default, all rules must be valid to return true unless the option atLeastOneValidRule is set to true.
* @param context
* @param rules
* @param options
*/
export function isContextValid(
context: Record<string, any>,
rules: Rule[],
options: {
someAreValid: boolean
} = {
someAreValid: false,
}
): boolean {
const { someAreValid } = options
const loopComparator = someAreValid ? rules.some : rules.every
const predicate = (rule) => {
const { attribute, operator, value } = rule
const contextValue = pickValueFromObject(attribute, context)
return operatorsPredicate[operator](
contextValue,
value as string & string[]
)
}
return loopComparator.apply(rules, [predicate])
}
/**
* Validate contextValue rule object
* @param rule
*/
export function validateRule(rule: Record<string, unknown>): boolean {
if (!rule.attribute || !rule.operator || !rule.value) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Rule must have an attribute, an operator and a value"
)
}
if (!isString(rule.attribute)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Rule attribute must be a string"
)
}
if (!isString(rule.operator)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Rule operator must be a string"
)
}
if (!availableOperators.includes(rule.operator as RuleOperator)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Rule operator ${
rule.operator
} is not supported. Must be one of ${availableOperators.join(", ")}`
)
}
if (rule.operator === RuleOperator.IN || rule.operator === RuleOperator.NIN) {
if (!Array.isArray(rule.value)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Rule value must be an array for in/nin operators"
)
}
} else {
if (Array.isArray(rule.value) || isObject(rule.value)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Rule value must be a string, bool, number value for the selected operator ${rule.operator}`
)
}
}
return true
}
export function normalizeRulesValue<T extends Partial<Rule>>(rules: T[]): void {
rules.forEach((rule) => {
/**
* If a string is provided, then we don't want jsonb to convert to the primitive value based on the RFC
*/
if (rule.value === "true" || rule.value === "false") {
rule.value = rule.value === "true" ? '"true"' : '"false"'
}
return rule
})
}
export function validateAndNormalizeRules<T extends Partial<Rule>>(rules: T[]) {
rules.forEach(validateRule)
normalizeRulesValue(rules)
}
/**
* Validate contextValue set of rules
* @param rules
*/
export function validateRules(rules: Record<string, unknown>[]): boolean {
rules.forEach(validateRule)
return true
}