# match-value Since JS as a language does not (yet) have a match statement or expression, this package implements the same concept for the most common cases. You specify a mapping of source values to result values, as an object, and `match-value` maps from one to the other, throwing an error if an invalid source value is specified, optionally having a catch-all for unknown values, and with support for lazily-produced result values. In other words, it turns this: ```js let mode; if (flag === "r") { mode = "readOnly"; } else if (flag === "rw") { mode = "readWrite"; } else if (flag === "a") { mode = "appendOnly"; } else { mode = `unknown:${someExpensiveCall(flag)}`; } ``` ... or this: ```js let mode; switch (flag) { case "r": mode = "readOnly"; break; case "rw": mode = "readWrite"; break; case "a": mode = "appendOnly"; break; default: mode = `unknown:${someExpensiveCall(flag)}`; break; } ``` ... into this: ```js const matchValue = require("match-value"); let mode = matchValue(flag, { r: "readOnly", rw: "readWrite", a: "appendOnly", _: () => `unknown:${someExpensiveCall(flag)}` }); ``` Less repetition, and a much clearer intention. ## Limitations - Source values may only be strings. Any input that isn't a string will always throw an error (or hit the catch-all, if you've specified one). - This is unfortunately to do with a limitation of JS as a language, that makes it impossible to ergonomically specify mappings where the source value isn't a string key. If JS ever gets Map literals, this limitation can be fixed. - Source values may only be literal values. There's no type/shape matching, like you might be used to if you've used a `match` construct in another language. - Again, this is due to a language limitation. - This library is completely unaware of the existence of Promises or asynchronous functions; all results are expected to be produced *synchronously*. - Of course, it is completely valid to return a Promise as your result value, and structure your code like `Promise.resolve(matchValue(input, { ... })` to consistently get a Promise out of it. ## License, donations, and other boilerplate Licensed under either the [WTFPL](http://www.wtfpl.net/txt/copying/) or [CC0](https://creativecommons.org/publicdomain/zero/1.0/), at your choice. In practice, that means it's more or less public domain, and you can do whatever you want with it. Giving credit is *not* required, but still very much appreciated! I'd love to [hear from you](mailto:admin@cryto.net) if this module was useful to you. Creating and maintaining open-source modules is a lot of work. A donation is also not required, but much appreciated! You can donate [here](http://cryto.net/~joepie91/donate.html). ## Example A full version of the example above: ```js "use strict"; const matchValue = require("match-value"); let flag = "a"; // Hardcoded for example let mode = matchValue(flag, { r: "readOnly", rw: "readWrite", a: "appendOnly", _: () => `unknown:${someExpensiveCall(flag)}` }); console.log(mode); // appendOnly ``` ## API ### matchValue(input, mapping) Converts the `input` to a result based on the `mapping`. - __input:__ The value to convert. - __mapping:__ An object that describes the mapping in the format `{ possibleInput: result }`. - A `possibleInput` of `_` defines a "catch-all" case; anything that doesn't match any of the other possible inputs, will trigger the catch-all case. - The `result` may be either a literal value, or a function that *produces* that value. If it's a function, the function will *only* be called when there is a match, and the return value of that call will be returned from the `matchValue` call. If you want to *return* functions rather than call them, see the `.literal` method below. - If a function is specified, it will be called with the original `input` as its first and only argument; this makes it easier to specify the mapping object in a different place from where it is used with `matchValue`, by not needing to have it in the same lexical scope. Returns a result if a match was found, or the catch-all was hit. Throws an `Error` if no match was found *and* no catch-all was specified. To throw a custom error type instead, define a catch-all function that throws that error, like so: ```js matchValue(input, { someKey: "someResult", someOtherKey: "someOtherResult", _: () => { throw new CustomError("Oh no!"); } }); ``` Note that if you're using an arrow function here, it *must* have curly braces around the `throw`; since a `throw` in JS is a statement, not an expression, it may not appear in a brace-less arrow function (which only accepts expressions). ### matchValue.literal(input, mapping) Exactly like `matchValue` above, but if the result value is a function, it will be *returned as-is*, rather than being called; that is, the function *is* the result. Note that this also means that a custom error-throwing handler is not possible in this case. ## Changelog ### v1.1.0 (June 3, 2020) * __Feature:__ A lazy function will now receive the original input value as its first and only argument. ### v1.0.1 (June 3, 2020) * __Bugfix:__ Fixed function handling; they were interpreted as literals in non-literal mode and vice versa. It now works correctly, according to what the documentation specifies. ### v1.0.0 (March 23, 2020) Initial release.