Hey Shokri, what is taking so long? Can I help you?

Shokri: Hey, man, I haven’t made much progress on this validation ticket because I’ve been distracted by many other things. Yesterday evening I realized that my knowledge of zod and vee-validate is limited, and I’m unsure if I can handle this alone.

So I have been I have a question about interfaces and factory functions. Can you help me understand them better?

Armin: I must admit that my knowledge of vee validate and zod is also limited. But let’s see what we can do!

He said, almost sure that the trouble was neither coming from zod nor vee-validate. When developers have trouble with a particular package, it’s rarely troubled with the package but the custom code around it.

Later that day, they are on the zoom call, Armin munching almost nonchalantly on the last bites of his belated lunch.

Shokri: So I know you like to use interfaces and factories a lot, but I’m not sure how I would do this. I understand that interfaces are a way to define a set of rules that a class must follow. But I’m not sure how they work with factory functions. And also, I am not sure how to use it where I need it.

Armin: Interfaces are a way to define rules but don’t necessarily have to be used with factory functions. However, factory functions can use interfaces to create objects that adhere to those rules.

Shokri: Oh, I see. So, when we use factory functions, do we create objects that implement specific interfaces?

Armin: Yes, that’s right. Factory functions can use interfaces to create objects that follow a specific set of rules. This allows us to create interchangeable objects that can be used in different parts of our program. But let’s take a look at your particular problem.

Shokri: So back to the problem now, there is the component that needs a validation rule, and we are generating it like so assuming typescript semantics,

const required_rules = createValidation('required')
const email_rules = createValidation('email')
const phone_number = createValidation('phone-number')

And we have functions like this everywhere, but I am unsure how to clean them up.

Armin: Ok, let’s start by encapsulating everything in a class:

class ValidationRuleFactory {
  static create(ruleConfiguration: ValidationRuleConfiguration): ValidationRule {
    switch (ruleConfiguration?.type) {
      case ValidationRuleType.REQUIRED:
        return new RequiredValidationRule(ruleConfiguration);
      case ValidationRuleType.EMAIL:
        return new EmailValidationRule(ruleConfiguration);
      case ValidationRuleType.PHONE:
        return new PhoneValidationRule(ruleConfiguration);
....
    }
  }
}

Your ValidationRuleConfiguration would like so

interface RequiredValidationRuleConfiguration {
  type: "RequiredValidationRule";
  errorText: string;
}
interface {
  type: "EmailValidationRule";
  errorText: string;
}
interface {
  type: "PhoneNumberValidationRule";
  errorText: string;
}

export type ValidationRuleConfiguration =
  | RequiredValidationRuleConfiguration
  | EmailValidationRuleConfiguration
  | PhoneNumberValidationRuleConfiguration

And All you need now is your interface(contract or protocol in other languages)

export interface ValidationRule {
  get(): (value: string) => boolean | string;
}

And your specific implementations must only implement this interface, and you are done.

class RequiredValidationRule implements ValidationRule {
  private readonly validator: (value: string) => void;
  constructor(ruleConfiguration: RequiredValidationRuleConfiguration) {
    this.validator = (value: string) => {
      if (value && value.trim()) return true;
      return ruleConfiguration.errorText;
    };
  }
  get(): (value: string) => boolean | string {
    return this.validator;
  }
}

Do we need vee-validate or zod now? are we saving time?

Shokri: Well, it was undoubtedly easier to install packages, as any other developer would do. But I get your point. The code is less; our bundles are smaller.

Armin: yes, some packages may contain security vulnerabilities that could put the website and its users at risk. Secondly, some packages may not be optimized for performance and could slow down the website’s loading times. Finally, some packages may not be compatible with others or dependencies, leading to conflicts and errors. And sometimes maintenance issues.

With zod, the contributing bundle size is ~45 kb. and the code would have looked like this:

const schema = zod.object({
    required: zod.string(),
    email: zod.string().email(),
    phoneNumber: zod.number().positive(),
}).strict();

The code is surely simpler, but 45 kb is the cost. Our code would have been around 5 kb, covering the same cases.


Leave a Reply

Your email address will not be published. Required fields are marked *