Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion - assertParseQueryString #815

Closed
nikitaeverywhere opened this issue Sep 14, 2023 · 14 comments
Closed

Suggestion - assertParseQueryString #815

nikitaeverywhere opened this issue Sep 14, 2023 · 14 comments
Assignees
Labels
enhancement New feature or request help wanted Extra attention is needed question Further information is requested

Comments

@nikitaeverywhere
Copy link

nikitaeverywhere commented Sep 14, 2023

Hello!

We're using your awesome library for input validation with (pretty much) express.js framework. Great job, neat solution! I would like to ask about implementing assertions for query strings.

In express (as well as everywhere else), when passing a query string ?foo=1&bar=2, req.query gets this object:

{
  "foo": "1",
  "bar": "2"
}

...which is validated by typia in our case with assertEquals(req.query).

I am now wondering whether we can do a step further and instead of defining

interface Query {
  foo: string;
  bar: string;
}

Make it be able to parse and validate against this interface:

interface Query {
  foo: string;
  bar: number; // parse as number!
}

To ultimately get this output with no validation errors:

{
  "foo": "1",
  "bar": 2
}

Feel free to close this request if it feels boring or keep it to collect feedback :) But this feature would allow to pass pretty much any (primitive) type in a query string and properly decode it. We have an automatically generated client library in our project which infers and reuses all types defined in the api, and having to always define strings in query even in case of a number feels like it can be fixed.

At the same time, I understand this can be very complex to implement.

Thanks!

@samchon samchon self-assigned this Sep 15, 2023
@samchon samchon added the invalid This doesn't seem right label Sep 15, 2023
@samchon
Copy link
Owner

samchon commented Sep 15, 2023

This is too domestic issue.

Instead, I can suggest you alternative solutions

@nikitaeverywhere
Copy link
Author

nikitaeverywhere commented Sep 15, 2023

This is too domestic issue.

What do you mean? Do you mean it's not for Typia?

Anyways thanks for suggestions. We don't use Fastify nor NestJS.

@samchon
Copy link
Owner

samchon commented Sep 19, 2023

Then can you suggest a function interface better than assertParseQueryString?

I think it would better to make it into a new namespace.

Also, restrictions would be like below:

https://nestia.io/docs/core/TypedQuery/#restriction

@samchon samchon added enhancement New feature or request help wanted Extra attention is needed question Further information is requested and removed invalid This doesn't seem right labels Sep 19, 2023
@nikitaeverywhere
Copy link
Author

nikitaeverywhere commented Sep 19, 2023

Then can you suggest a function interface better than assertParseQueryString?

If doing it in a proper way, by analogy with json parse functions in Typia, I'd suggest

export namespace queryString {
    export function isParse<T>(input: string): Primitive<T> | null;
    export function assertParse<T>(input: string): Primitive<T>;
    export function validateParse<T>(
        input: string
    ): IValidation<Primitive<T>>;
}

we could inherit URLSearchParams's API but it looks like it's better to use just plain object with parsed query params, i.e.

interface Type {
  a: number[];
  b: 'foo' | 'bar';
}

typia.queryString.assertParse<Type>('?a=1&b=foo&a=3'); // Fine, { a: [1, 3], b: "foo" }
typia.queryString.assertParse<Type>('?a=1&b=foo'); // Fine, { a: [1], b: "foo" }
typia.queryString.assertParse<Type>('?a=1&b=foo&a=uh-oh'); // Invalid
typia.queryString.assertParse<Type>('?a=1&b=buzz'); // Invalid

@samchon
Copy link
Owner

samchon commented Sep 21, 2023

No encoding function required?

@nikitaeverywhere
Copy link
Author

No encoding function required?

Do you mean object -> string encoding? For our (API) needs, no. On the client, our encoding function has quite trivial implementation. From the PoV of Typia library, for the sake of the completeness it's fine to use this api.

@samchon
Copy link
Owner

samchon commented Sep 26, 2023

#819

Upgrade to v5.1, then you can use it.

@nikitaeverywhere
Copy link
Author

You're 🔥! Thank you! Seems to work flawlessly.

However I've noticed this in the comment to httpTypia.query:

Also, typia.http.query() function does not perform validation about the decoded value. Therefore, if you can't sure that input data is following the T type, it would better to call one of below functions intead.

That text in bold actually is scary. What's the purpose of httpTypia.query then if it can't ensure type T? Can you please give an example when it happens?

P.S. We also can't migrate to v5 as customValidators seem to be dropped. Documenting any alternatives would be much appreciated 🙌

@samchon
Copy link
Owner

samchon commented Sep 27, 2023

You're 🔥! Thank you! Seems to work flawlessly.

However I've noticed this in the comment to httpTypia.query:

Also, typia.http.query() function does not perform validation about the decoded value. Therefore, if you can't sure that input data is following the T type, it would better to call one of below functions intead.

That text in bold actually is scary. What's the purpose of httpTypia.query then if it can't ensure type T? Can you please give an example when it happens?

P.S. We also can't migrate to v5 as customValidators seem to be dropped. Documenting any alternatives would be much appreciated 🙌

Then you typia.http.assertQuery() or similar functions instead.

If you read a little bit more about the comment, you may find the way.

@nikitaeverywhere
Copy link
Author

nikitaeverywhere commented Sep 27, 2023

Then you typia.http.assertQuery() or similar functions instead.

Does it literally mean to do this?

interface MyQuery {
  a: number;
  b: string;
}

const myQuery = http.query<MyQuery>(req.originalUrl);
assertQuery(myQuery);

// ... 

If so, I can't think of a use case to be honest (in the HTTP api at least) where http.query will be used alone (as it doesn't guarantee the type safety for some reason AFAIU).

@samchon
Copy link
Owner

samchon commented Sep 27, 2023

Like this:

https://typia.io/playground/?script=JYWwDg9gTgLgBDAnmYBDANHA3g1BzAZzgF84AzKCEOAIiRVRoG4AoF4AOxgFMozUAxtzgBJALLcQAI17YWcBXGAATAFxwCMKJzxwAZLkIA6AGLQQqGAB4aAV1sqaAPlaK4k1MAA26zdo66BjD4BKbmljYe3s6uivjc6hy20rLybopBIUYAKsjcNg5cAMwATM5waen6hqFinKDJViUADE4VVQqZxgCiAB4CXrYEwABu3GKovQ0gVgCMza2sxGz0aEYAFjAwYEYCUNyW3ACCBAS8MACKtryIVuKSMlBOABQAlKwsQA

import typia, { tags } from "typia";

interface IMember {
    id: string & tags.Format<"uuid">;
    email: string & tags.Format<"email">;
    age: number 
        & tags.Type<"uint32"> 
        & tags.Minimum<20> 
        & tags.ExclusiveMaximum<100>;
}

typia.http.createAssertQuery<IMember>();

@nikitaeverywhere
Copy link
Author

Thanks! Could you please clarify the end-application usage of this 😇 Am I correct it would be the following:

import typia, { tags } from "typia";

interface IMember {
    id: string & tags.Format<"uuid">;
    email: string & tags.Format<"email">;
    age: number 
        & tags.Type<"uint32"> 
        & tags.Minimum<20> 
        & tags.ExclusiveMaximum<100>;
}

const assertQuery = typia.http.createAssertQuery<IMember>();

// Custom function that wraps, for instance, Express js framework or whatever
defineMyApiEndpoint(async (req, res) => {
  const myQuery = http.query<IMember>(req.originalUrl);
  assertQuery(myQuery);
  
  // work with myQuery
});

@samchon
Copy link
Owner

samchon commented Sep 27, 2023

Oh my god, what the hell are you doing?

Just choose one of below:

import typia, { tags } from "typia";

interface IMember {
    id: string & tags.Format<"uuid">;
    email: string & tags.Format<"email">;
    age: number 
        & tags.Type<"uint32"> 
        & tags.Minimum<20> 
        & tags.ExclusiveMaximum<100>;
}

const assertQuery = typia.http.createAssertQuery<IMember>();

// Custom function that wraps, for instance, Express js framework or whatever
defineMyApiEndpoint(async (req, res) => {
  const myQuery = assertQuery(req.originalUrl);
  
  // work with myQuery
});
import typia, { tags } from "typia";

interface IMember {
    id: string & tags.Format<"uuid">;
    email: string & tags.Format<"email">;
    age: number 
        & tags.Type<"uint32"> 
        & tags.Minimum<20> 
        & tags.ExclusiveMaximum<100>;
}

// Custom function that wraps, for instance, Express js framework or whatever
defineMyApiEndpoint(async (req, res) => {
  const myQuery = typia.http.assertQuery<IMember>(req.originalUrl);
  
  // work with myQuery
});

@nikitaeverywhere
Copy link
Author

😅

Thank you a ton, this explains everything.

So far we actually used it like this:

import { assertEquals } from 'typia';

apiAuthenticatedEntry<ApiRequestBody, ApiResponseBody>(async (req, res) => {
  assertEquals(req.body); // req.params is typed to ApiRequestBody and typia infers this type for a check
  
  // work with req.body - they're already parsed and typed by the underlying framework,
  // and we use typia just for the runtime check
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants