@rustable/enum
Version:
Rust-inspired pattern matching and type-safe error handling for TypeScript. Includes Option<T> for null-safety and Result<T, E> for error handling, with comprehensive pattern matching support
219 lines (215 loc) • 6.71 kB
JavaScript
'use strict';
var utils = require('@rustable/utils');
"use strict";
function variant(target, name, descriptor) {
descriptor.value = function(...args) {
return new target(name, ...args);
};
return descriptor;
}
exports.Enums = void 0;
(function(Enums2) {
function create(arg1, arg2) {
const Anonymous = class extends Enum {
};
if (arg2) {
if (typeof arg1 === "string") {
Object.defineProperty(Anonymous, "name", {
value: arg1,
writable: false,
configurable: false
});
} else {
throw new Error("Invalid arguments for create function");
}
} else if (typeof arg1 === "string") {
throw new Error("Invalid arguments for create function");
}
const variants = arg2 || arg1;
for (const [variantName] of Object.entries(variants)) {
Object.defineProperty(Anonymous, variantName, {
value: (...args) => new Anonymous(variantName, ...args),
writable: false,
configurable: false
});
Object.defineProperty(Anonymous.prototype, `is${variantName}`, {
value: function() {
return this.is(variantName);
},
writable: false,
configurable: false
});
Object.defineProperty(Anonymous.prototype, `let${variantName}`, {
value: function(callback) {
return this.let(variantName, callback);
},
writable: false,
configurable: false
});
}
return Anonymous;
}
Enums2.create = create;
})(exports.Enums || (exports.Enums = {}));
class Enum {
constructor(name, ...vars) {
this.name = name;
this.vars = vars;
}
/**
* Checks if the enum is a specific variant
* @param variant The variant name to check
* @returns true if the enum is the specified variant
*/
is(variant2) {
return this.name === variant2;
}
/**
* Checks if the enum is a specific variant and executes a callback if it matches
* @param variant The variant name to check
* @param callback The callback function to execute if the variant matches
* @returns The result of the callback if variant matches, undefined otherwise
*/
let(variant2, cb) {
return this.is(variant2) ? cb.if(...this.vars || []) : typeof cb.else === "function" ? cb.else() : cb.else;
}
/**
* Unwraps the first argument of a variant
* @throws Error if the variant has no arguments
* @returns The first argument of the variant
*/
unwrap() {
if (!this.vars || this.vars.length === 0) {
throw new Error("Cannot unwrap a variant without arguments");
}
return this.vars[0];
}
/**
* Unwraps all arguments of a variant as a tuple
* @throws Error if the variant has no arguments
* @returns Tuple of all variant arguments
*/
unwrapTuple() {
if (!this.vars || this.vars.length === 0) {
throw new Error("Cannot unwrap a variant without arguments");
}
return [...this.vars];
}
/**
* Converts the enum to a string representation
* Format: VariantName for variants without arguments
* Format: VariantName(arg1, arg2, ...) for variants with arguments
*/
toString() {
if (!this.vars || this.vars.length === 0) {
return this.name;
}
return `${this.name}(${this.vars.join(", ")})`;
}
/**
* Pattern matches on the enum variant, similar to Rust's enum expression
* Use this method to handle different variants of the enum in a type-safe way.
*
* @param patterns Object mapping variant names to handler functions
* @param defaultPatterns Optional default patterns to use if a variant isn't matched
* @throws Error if no matching pattern is found and no default pattern is provided
* @example
* ```typescript
* enum.enum({
* Success: (value) => `Got ${value}`,
* Error: (err) => `Error: ${err.message}`,
* })
* ```
*/
match(patterns) {
const variantName = this.name;
const handler = patterns[variantName] ?? patterns["_"];
if (typeof handler === "undefined") {
throw new Error("No handler found.");
}
if (typeof handler !== "function") {
return handler;
}
const fn = handler;
if (!this.vars || this.vars.length === 0) {
return fn();
}
return fn(...this.vars);
}
/**
* Checks if this enum instance equals another enum instance
* Compares both variant names and their arguments
*/
eq(other) {
if (!(other instanceof this.constructor)) {
return false;
}
const { name: thisName, vars: thisArgs } = this;
const { name: otherName, vars: otherArgs } = other;
if (thisName !== otherName) {
return false;
}
if (!thisArgs && !otherArgs) {
return true;
}
if (!thisArgs || !otherArgs) {
return false;
}
if (thisArgs.length !== otherArgs.length) {
return false;
}
return thisArgs.every((arg, i) => {
const otherArg = otherArgs[i];
if (typeof arg === "object" && "eq" in arg) {
return arg.eq(otherArg);
}
return utils.equals(arg, otherArg);
});
}
/**
* Creates a deep clone of the current enum instance
* @returns A new instance of the enum with the same variant and cloned arguments
*/
clone(hash = /* @__PURE__ */ new WeakMap()) {
const Constructor = this.constructor;
if (!this.vars || this.vars.length === 0) {
return new Constructor(this.name);
}
const clonedArgs = this.vars.map((v) => utils.deepClone(v, hash));
return new Constructor(this.name, ...clonedArgs);
}
/**
* Replaces the current variant with a new one, returning the old variant
* @param newVariant The new variant to replace with
* @param ...args Arguments for the new variant
* @throws Error if the new variant is not a valid variant of this enum
* @returns The old variant instance
*/
replace(newInstance) {
if (!(newInstance instanceof this.constructor)) {
throw new Error("Invalid instance type");
}
const oldVariant = new this.constructor(this.name, ...this.vars || []);
this.vars = [...newInstance.vars];
this.name = newInstance.name;
return oldVariant;
}
/**
* Modifies the arguments of the current variant based on the variant name
* @param patterns Object mapping variant names to modifier functions
* @throws Error if no matching pattern is found for the current variant
*/
modify(patterns) {
const variantName = this.name;
const modifier = patterns[variantName];
if (!modifier) {
return;
}
if (!this.vars || this.vars.length === 0) {
return;
}
this.vars = modifier(...this.vars);
}
}
exports.Enum = Enum;
exports.variant = variant;