How do I make the crypto.subtle.digest algo flexible?
This is a learning moment, bear with me. 😅
I can hard code an algo for the digest like this:
But if I want to make the algo flexible, it forces me to make it a DigestAlgorithm and it cannot be a string shown here:
I reviewed what that is in the source:
https://github.com/denoland/deno_std/blob/main/crypto/_wasm/mod.ts
I see this:
export type DigestAlgorithm = typeof digestAlgorithms[number];
(I don't quite see what is going on here and am interested.)
Bottom line is, when I make algo:string a algo:DigestAlgorithm in the digest function param list, it stops complaining, but I need to have some calling code somewhere be a flexible string (or maybe an enum if necessary) that somehow translates to a DigestAlgorithm for this to work.
I've been struggling with this. Any ideas?18 Replies
That's because not any random string is a valid algorithm. The string
asdf
for example is not a valid algorithm for the crypto module
But typing it as string
would mean that this would be valid, which leads to a type conflict
the type string
is too wide
only particular strings are valid, and those are defined in the digestAlgorithms
arrayDigestAlgorithm
is still a string, just a very specific string from one of the digestAlgorithms
. E.g.
This prevents your code from using an unsupported (or nonsense) algorithm.I agree and understand why. My goal is to understand how to make it flexible. Am I forced to hard code this value?
By hard code, do you mean
digestAlgorithms
? No, you simply import DigestAlgorithm
which is a type matching one of the above constants. What is this code for? A library? A web app? Can you provide more context? This is as flexible as possible:
and trying this gives a compile error when using Typescript:
@cknight I am writing the beginnings of a deterministic pseudo random library that allows the user of the library to specify what algorithm to use. It would be a separate issue to discuss the merits of such a library. (Very happy to discuss!) The best that I can do to make this flexible is by doing the following:
I think I was hoping Deno and/or TypeScript would allow for something like the following so that there is no need for all the "if" statements, which is error prone and tedious. More importantly, whenever Deno adds a new one to DigestAlgorithm (they added "BLAKE2B-128" 2 months ago), I would also need to change the code to add another "if" statement. What I was hoping for is something kind of like this:
If something along those lines can't be accomplished, I'm seeing this as a wider language issue. DigestAlgorithm is just an example. The same problem would come up elsewhere.
You don't need all these if statements if you type
algo
correctly. The problem is in your new method, because there it's typed as string
.
That is true, but then the calling code would need to have the "if" statements.
if the value is provided by the user or something you can use a type guard:
That way you can tell TS that this is of type
DigestAlgorithm
See the relevant section in the TS docs https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates
Documentation - Narrowing
Understand how TypeScript uses JavaScript knowledge to reduce the amount of type syntax in your projects.
The "str is DigestAlgorithm" is interesting and might be what allows DigestAlgorithm.includes(algo) to work. How would I implement that on my "new" method above?
Something like this doesn't quite work:
static async new({algo, seed}: {algo: string, seed: string}): Promise<Random>, algo is DigestAlgorithm {
Why?
await Random.new({algo: "SHA-256", seed: "my seed string"});
would compile fine, but await Random.new({algo: "blah blah blah", seed: "my seed string"});
would not, if the signature is as Marvin describedMaybe the algo comes from the database or maybe it comes from user input or somewhere else. Something needs to check it against DigestAlgorithm somewhere and the best that I could see how to do that until now was with "if" statements or something.
What @marvinh. seemed to indicate is there is a way to cast algo to a DigestAlgorithm in the method signature that could then be used with "DigestAlgorithm.includes", which is interesting and might be what is needed here.
you should never cast types in the method signature if not necessary. Types are there to make invalid states possible. Passing any random string to your
Random
class is not valid, so you should really drop the algo: string
type because not any string is a valid algorithm. That solves all of your problems.
The casting is only necessary if you receive the value from untrusted input like from an http request or something. Those kinds of type guards/assertions should only live at the outer edge of your program or library.Yes, I understand and agree with casting being necessary when input is coming from untrusted source and should only live at outer edge of program or library. The
new
method is the outer edge of the library where the input comes in.
I am attempting to work in the DigestAlgorithm.includes(algo)
that you showed above, but I am getting this error here:
'DigestAlgorithm' only refers to a type, but is being used as a value here.deno-ts(2693)
If it is recommended that the new
method here only has algo as DigestAlgorithm, I could abide by that too, ok. But in any case, I'm getting that issue when attempting to use includes
on DigestAlgorithm
.Ah looks like that's only a type in the std lib
One way or another, I am hoping to get away from needing to have all those "if" statements and I am more than happy to adhere to best practices that you are sharing with me.
In that case use the type guard. Here is a full example:
Oh nice! I wasn't even looking at the lower cased
digestAlgorithms
that allows you to call includes on it. Yes, your guard approach is excellent and works nicely. Thanks for this and the insights!