ecyrbe / zodios

typescript http client and server with zod validation
https://www.zodios.org/
MIT License
1.7k stars 46 forks source link

[Proposal] Create a way for Query Parameters to have a custom operator other than just "=" #475

Closed cloudbring closed 1 year ago

cloudbring commented 1 year ago

First, love this project and being able to define APIs in a type-safe way.

The issue I'm trying to solve is I have to support a bunch of query parameters that aren't just /foo?field=value.

Example Query Parameters:

LOTR API Documentation

Type Example
match /character?name=Legolas
negate match /character?name=!Frodo
include /character?race=Hobbit,Dwarf
exclude /character?race!=Orc,Goblin
exists /character?name
not exists /character?!name
regex /character?name=/^Fro/i
negate regex /character?name!=/^Fro/i
greater than /character?height>180
less than /character?height<180
greater than or equal /character?height>=180
less than or equal /character?height<=180
equal to /character?height=180

Proposal

If we define a url query as consisting of a field, a value and an operator, I want to support various operators.

  1. Create an optional operator field on the parameters object.

    const params = makeParameters([
      //=> /character?name=!Legolas
      {
        field: 'name',
        description: 'Filter results by name of character',
        type: 'Query',
        operator: '!=', // Optional: Defaults to '='
        schema: z.string()
      },
    ]);

    Usage:

    const response = await zodios.request({
      method: 'GET',
      url: '/character',
      data: { name: 'Legolas', operator: '!='}
    })
  2. Create a new RAW_QUERY query type that exposes the full parameter string (everything between and excluding the ? and & characters).

    const params = makeParameters([
      //=> /character?name!=/^Fro/i&...
      {
        field: 'name',
        description: 'Filter results by regex of name of character',
        type: 'RAW_QUERY',
        rawQuery: z
        // ! NOTE: The object members should are customizable
        .object({
          field: z.string().default('name'), // Ideally this would be optional and inferred from the parent object field 'name'
          operator: z.string().default('!='), // Literal here but, can be a zobject to allow a function to create a query parameter.
          regex: z.string(), // Zod needs an isRegex() function
        })
        .transform((data: {field,operator,regex}) => 
          `${field}${operator}${regex}`,
        ),
      },
    ])

    Usage:

      //=> /character?name!=/^Fro/i&...
      const response = await zodios.request({
        method: 'GET',
        url: '/character',
        data: { 
          field: 'name', // Optional: Defaults to 'name'
          operator: '!=', // Optional: Defaults to '!='
          regex:'/^Fro/i' 
        }
      });
cloudbring commented 1 year ago

Would this be better implemented as a plugin, or will this require a PR to Zodios itself?

ecyrbe commented 1 year ago

Hello,

Thank you for the proposal, Since query parameters are not standardized at all, Zodios implements the most common pattern in practice. So any custom format should be implemented in a plugin. Zodios will not provide one. Since there are hundred of custom query formats in the wild, and zodios can't support them all. So i suggest you implement your custom serializer in your code base as a plugin. No need to do a PR to zodios. You can already add your custom 'operator' to your api definition, zodios and typescript will not complain. You can then write your plugin to use the 'operator' keyword and forward your serializer to axios : See paramsSerializer in axios documentation on how to implement a custom query param serializer.

cloudbring commented 1 year ago

Thanks for the feedback. Once I'm done with my current project, I'll take a stab at making a Zodios plugin for this using paramsSerializer.

stale[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

ecyrbe commented 1 year ago

closing. But feel free to share the results with the community