JSFiddle - React, Tailwind, and code Playground

by Paul

JavaScript

// Core boolean rules that need to be implemented in all clients, can't be overriden
const coreRules = {
  true: () => true,
  false: () => false,
  not: (check, context, rules) => !check(context, rules),
  all: (check, context, rules) => rules.every(r => check(context, r)),
  any: (check, context, rules) => rules.some(r => check(context, r)),
};

// Context-aware rules where the client can apply business logic
const clientRules = {
  user_is_native: (check, context) => context.user.type === 'native',
  user_is_email: (check, context) => context.user.type === 'email',
  session_has_email: (check, context) => context.session.has_email,
  conversation_has_email: (check, context) => context.conversation.has_email,
  // By default the client allows to send messages
  can_send_message: (check, context) => true,
};

// Server rules need to be serialisable as JSON data
const serverRules = {
  // Server can create custom rules by composing existing ones
  conversation_hasnt_email: ['not', 'conversation_has_email'],
  // The server overrides the client rule to send message based on other rules
  can_send_message: [
    'any',
    'conversation_hasnt_email',
    ['all', 'conversation_has_email', 'session_has_email'],
  ],
};

// Transform rules object into functions
const processRules = rules => Object.keys(rules).reduce((acc, key) => {
  acc[key] = (check, context) => check(context, rules[key]);
  return acc;
}, {});

// Merge all rules
const allRules = Object.assign({}, clientRules, processRules(serverRules), coreRules);

// Main checking logic
const checker = (context, rules) => {
  if (!Array.isArray(rules)) {
    rules = [rules];
  }
  if (!rules.length) {
    return false;
  }
  const callRule = allRules[rules.shift()];
  return callRule ? callRule(checker, context, rules) : false;
};

// Example data for demo
const mock = {
  user: {
    type: 'native',
  },
  session1: {
    has_email: true,
  },
  session2: {
    has_email: false,
  },
  conversation1: {
   ...