samchon / typia

Super-fast/easy runtime validators and serializers via transformation
https://typia.io/
MIT License
4.45k stars 153 forks source link

Runtime variable default values (with tags.default) #963

Open RobbyUitbeijerse opened 6 months ago

RobbyUitbeijerse commented 6 months ago

Hello! We're exploring migrating our zod based DTOs to typia/nestia, but while doing a bit of exploring I ran into the following case which I couldn't quite solve yet.

We have an input model that requires a chainId to be passed. chainId is a number, but only valid numbers from an environment variable are acceptable. We currently handling that like so and it works perfect!

type ChainId = tags.TagBase<{
  kind: 'chainId';
  target: 'number';
  value: undefined;
  validate: `String(process.env.SUPPORTED_IDS).split(',').includes(String($input))`;
}>;

export type AddChainToGameRequestInput = {
  chainId: number & tags.Default<13337> & ChainId;
};

Question

What I couldn't figure out however, is how to pass a default value to the chainId that also comes from a runtime environment, for example: an environment variable. The reason we would want this, is because process.env.SUPPORTED_IDS will be completely different per environment we run our application.

In the snippet above, you can see that we simply pass a hardcoded 13337 number now:

export type AddChainToGameRequestInput = {
  chainId: number & tags.Default<13337> & ChainId;
};

Is it somehow possible for typia to process default values based on a runtime variable? or is that outside of the scope for this project? Sorry if it has been asked before, but I couldn't quite find an issue related.

To clarify, we would want something like this, which currently (for obvious reasons) wouldn't quite work

  chainId: number & tags.Default<process.env.DEFAULT_CHAIN_ID> & ChainId;

I could technically see it work in similar fashion to how tags.TagBase handles validate, so something like a default option to call during runtime, of which the return value gets used as the default value.

Something like:


type Model = {
  chainId: number & tags.Default<{
      dynamic: `process.env.DEFAULT_CHAIN_ID`;
    }>;
}

Let me know if you like this idea, I could give it a swing at developing a PR.

samchon commented 6 months ago

The default tag has been designed to support only the JSON schema generation.

RobbyUitbeijerse commented 6 months ago

Would there be another way to get a runtime variable to be a default value to a property in a type? We want it to be coming from the runtime, as the default value is different depending on the environment.

With zod, we do it like so:

const chainIdSchema = z
  .number()
  .default(Number(process.env.CHAIN_ID));

Is there an equivalent of that in Typia?

samchon commented 6 months ago

No

RobbyUitbeijerse commented 6 months ago

Is it something you would consider a good addition? Due to the runtime validation aspect of Typia it does feel logically to me to also have runtime default values instead of just static ones, but it might just be me or just out of scope

samchon commented 6 months ago

https://github.com/samchon/typia/issues/979

I'm planning to implement the default value filling feature through transformer (raw data to class instance).

It is not a matter even the target type is not a class type, but cannot sure when I can start it.

ryoppippi commented 2 months ago

Hi I have an idea for this. Instead of adding default variable feature, how about adding a function like this:

This solution is simpler than implementing default tag, and we can also set a different value each time as the default value for each implementation.

This is similar to the maybe function in unknownutil https://github.com/jsr-core/unknownutil#maybe

We can implement it like this.

function maybe<T>(input: unknown): T | undefined {
    return typia.is<T>(input) ? input : undefined
}

type Model = {
  chainId: number;
}

const input: unknown = { }
const res: Model = maybe({}) ?? ({ chainId: process.env.DEFAULT_CHAIN_ID })
console.log(res) // -> env variable here!

We can implement this every time we use it. It may be even more useful to have this as a built-in function of typia.

This is my personal opinion, but TypeScript types should have only information about the structure of the data