chore(): Reorganize modules (#7210)
**What** Move all modules to the modules directory
This commit is contained in:
committed by
GitHub
parent
7a351eef09
commit
4eae25e1ef
249
packages/modules/fulfillment/src/utils/__tests__/utils.spec.ts
Normal file
249
packages/modules/fulfillment/src/utils/__tests__/utils.spec.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
1
packages/modules/fulfillment/src/utils/index.ts
Normal file
1
packages/modules/fulfillment/src/utils/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './utils'
|
||||
172
packages/modules/fulfillment/src/utils/utils.ts
Normal file
172
packages/modules/fulfillment/src/utils/utils.ts
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user