Git Product home page Git Product logo

openapi-codegen's Introduction






npm Read the documentation

Tooling to give you full type-safety around OpenAPI specs.

You can generate

Getting started

  1. Initialize the generator

    $ npx @openapi-codegen/cli init

    If you wish to change any of the selections made, you can do so in the generated openapi-codegen.config.ts file later..

  2. Start Generation

    $ npx openapi-codegen gen {namespace}

    After the code generation is done, you will notice the following files:

    • {namespace}Fetcher.ts - defines a function that will make requests to your API.
    • {namespace}Context.tsx - the context that provides {namespace}Fetcher to other components.
    • {namespace}Components.tsx - generated React Query components (if you selected React Query as part of initialization).
    • {namespace}Schemas.ts - the generated Typescript types from the provided Open API schemas.

     

    Warning

    If {namespace}Fetcher.ts or {namespace}Context.tsx already exist in the output folder, they will not be replaced. However, {namespace}Components.tsx and {namespace}Schemas.ts will be re-generated each time based on the Open API spec file provided.

  3. Configure the Fetcher (optional)

    After the first step you should see a file called {namespace}Fetcher.ts in your ouput directory. This file

    By default it uses the built-in Fetch API, you are free to change this to your fetching library of choice (Axios, Got etc.)

    If your Open API spec contains a configured server, then the base URL for all requests will default to that server's URL. If no such configuration exists, you'll need to specify the base URL value.

  4. Install and Configure React Query (optional)

    If during generator setup you picked > React Query components, then you will need to install and configure React Query in order for the generated React hooks to work properly:

    • Install the library
      npm i @tanstack/react-query
    • Wire up the QueryClient as described here.

Philosophy

In software development, communication between components and documentation around it is often no fun.

GraphQL did resolve this by making documentation a part of the tooling (introspection), sadly this is often harder with REST APIs. OpenAPI can be an amazing tool, if, and only if the documentation (spec) and the actual implementation are aligned!

Backend side

There are two different approaches:

  1. The OpenAPI spec is generated from the code (code first)
  2. The code is generated from the OpenAPI spec (spec first)

In either case, there needs to be an integration with the type system of the language, so everything is connected, and as we remove or update something that impacts the final response, this is automatically reflected!

This library has chosen the second approach, spec first. By doing so, your documentation is not your final (boring) task on the list, but the first and exciting one when adding new functionality! Indeed, you can’t start coding without generating your types (models & controllers) from the specs.

This has multiple benefits:

  • You can take your time to think about your API before writing any code!
  • You can discuss the API with your team (and discover API design problems earlier)
  • You can generate all your validation rules

For example, if you add this object to your schema:

SignUpInput:
  type: object
  properties:
    email:
      type: string
      format: email
      maxLength: 255
    password:
      type: string
      maxLength: 255
    firstName:
      type: string
      pattern: ^[0-9a-zA-Z]*$
      maxLength: 255
    lastName:
      type: string
      pattern: ^[0-9a-zA-Z]*$
      maxLength: 255
  required:
    - email
    - password
    - firstName
    - lastName

OpenAPI Codegen will be able to generate all the relevant validation (or at least give you the choice to do it).

Note You can also attach any custom logic by using the x-* tag, the possibilities are endless!

Frontend side

Having to reverse engineer a backend response is the least productive/fun task ever! However, given a nice OpenAPI specs, we can actually generate nicely typed code for you that lets you interact with your API in a safe manner.

Taking React as example, calling an API can be as simple as this: (this hooks are using Tanstack Query under the hood)

import { useListPets } from "./petStore/petStoreComponents"; // <- output from openapi-codegen

const Example = () => {
  const { data, loading, error } = useListPets();

  // `data` is fully typed and have all documentation from OpenAPI
};

Note You can also check this blog post about using generated hooks in React https://xata.io/blog/openapi-typesafe-react-query-hooks

And since this generated from the specs, everything is safe at build time!

Note If you can’t trust your backend, some runtime validation can be useful to avoid surprises in production 😅

Configuration

The only thing you need to manage is the configuration. Everything is typed and self-documented, but just in case, you can find here example configuration below:

Example Config

// openapi-codegen.config.ts
import { defineConfig } from "@openapi-codegen/cli";
import {
  generateSchemaTypes,
  generateReactQueryComponents,
  /* generateExpressControllers, */
  /* generateRestfulReactComponents, */
  /* ... */
} from "@openapi-codegen/typescript";

export default defineConfig({
  example: {
    // can be overridden from cli
    from: {
      source: "github",
      owner: "fabien0102",
      repository: "openapi-codegen",
      ref: "main",
      specPath: "examples/spec.yaml",
    },

    // can be overridden from cli
    outputDir: "src/queries",

    to: async (context) => {
      // You can transform the `context.openAPIDocument` here, can be useful to remove internal routes or fixing some known issues in the specs ;)

      // Generate all the schemas types (components & responses)
      const { schemasFiles } = await generateSchemaTypes(context, {
        /* config */
      });

      // Generate all react-query components
      await generateReactQueryComponents(context, {
        /* config*/
        schemasFiles,
      });
    },
  },
});

Plugins

the @openapi-codegen/cli supports these generator plugins:

generateSchemaTypes (frontend/backend)

generate all schema types for your specification:

 const { schemasFiles } = await generateSchemaTypes(context, {
      /* config */
    });

output: {namespace}Schemas.ts

generateFetchers (frontend)

generate all fetchers with types for your specification needs schemafiles

   await generateFetchers(context, {
      /* config */
      schemasFiles,
    });

output: {namespace}Fetchers.ts

generateReactQueryComponents (frontend)

generate all React Query Components for useQuery() and useMutation()

    await generateReactQueryComponents(context, {
      /* config*/
      schemasFiles,
    });

output: {namespace}Components.ts

generateReactQueryFunctions (frontend)

generate all React Query Functions used for e.g. React-Router 6.6.0+ loader functions

   await generateReactQueryFunctions(context, {
      filenamePrefix,
      schemasFiles,
    });

output: {namespace}Functions.ts

example usage in react-route-loader:

export const routeLoader = (queryClient: QueryClient) =>
  async ({ params }: MyParams) => 
    await queryClient.fetchQuery(...getYourQueryNameQuery({}), {
      /*options*/
    })

more infos: https://reactrouter.com/en/main/guides/data-libs

You can import any generator into the to section, those can be the ones provided by this project or your own custom ones. You have full control of what you are generating!

Have fun!

Contributors ✨

Thanks goes to these wonderful people (emoji key):


Fabien BERNARD

💻 🎨 📖 🤔 📆 👀

mpotomin

💻 🤔 👀

Michael Franzkowiak

📖

Alexis Rico

💻 🤔

Nedim Arabacı

💬 💻 🤔

Antoniel Magalhães

💡 🐛

Florian Dreier

💻

Fabian Althaus

💻

ci-vamp

🐛 💻

Alan Oliveira

💻

This project follows the all-contributors specification. Contributions of any kind welcome!

openapi-codegen's People

Contributors

alan-oliv avatar allcontributors[bot] avatar antoniel avatar ci-vamp avatar dependabot[bot] avatar dreierf avatar el-j avatar fabien0102 avatar fb55 avatar fujikky avatar github-actions[bot] avatar karnak19 avatar kev2480 avatar macyabbey-okta avatar micha-f avatar needim avatar nikravi avatar rajzik avatar sferadev avatar sidd27 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

openapi-codegen's Issues

Enhancement: add eslint and tslint ignore/disable to generated files

Hi all,

First of all, great library!
I would like to suggest a simple enhancement over the current codebase that would help in the different dev environments, which would be adding tags to disable eslint and tslint in auto-generated files (since these might trigger formatting errors).

If I'm not wrong, the following at the top of each of the files should suffice:
/* eslint-disable */
/* tslint:disable */

I'm not completely sure if it's possible to customize it and add it current as it is, but I think this would be pretty useful.

You can check about this functionality from another library that autogenerates the client as well, for reference: https://github.com/acacode/swagger-typescript-api/blob/a6f16d7097db6df2c3d657eff3cc6786cef52331/tests/spec/readonly/schema.ts

Cheers

How to check the status code of a received error inside a hook?

I am currently switching from a custom axios client to the openapi-codegen generated client.
For that, I need to check the status code of the returned error. I have found an issue and a pull request which might be related, but I still wasn't able to make it work the way I wanted it to.

This is my schema from the /api/v1/auth/session/ endpoint.
image

And this is my used code. The error was correctly extracted from the openapi schema.
image

But even with a 403 status code and {"detail":"Authentication credentials were not provided."} as response body.
I always get an unknown status and generic message:
image
It seems like I need to edit the fetcher.ts but I am unsure how. Maybe someone can point me in the right direction.

typescript fetcher error handler always returns error status "unknown"

I cannot see how the code in the fetcher is supposed to pass on any error it gets in the response.

In https://github.com/fabien0102/openapi-codegen/blob/13ca9b35ab0bbd363946baf4a015b3e6d317f728/plugins/typescript/src/templates/fetcher.ts

we have

  try {
...
    if (!response.ok) {
      let error: ErrorWrapper<TError>;
      try {
        error = await response.json();
      } catch (e) {
        error = {
          status: "unknown" as const,
          payload:
            e instanceof Error
              ? \`Unexpected error (\${e.message})\`
              : "Unexpected error"
        };
      }
      throw error;
    }
    if (response.headers.get('content-type')?.includes('json')) {
      return await response.json();
    } else {
      // if it is not a json response, assume it is a blob and cast it to TData
      return (await response.blob()) as unknown as TData;
    }
  } catch (e) {
    throw {
      status: "unknown" as const,
      payload:
        e instanceof Error
          ? \`Network error (\${e.message})\`
          : "Network error"
    }
  }
}

but because the inner throw error is itself inside a try...catch - the outer catch will always catch it, and because it is not an instance of Error it will always give just

{
  "status": "unnown",
  "payload": "Network error"
}

which is not very useful.

I would suggest to dispense with the outer try...catch and use something simpler like:

  if (!response.ok) {
    let error: ErrorWrapper<TError>;
    try {
      error = {
        status: response.status,
        payload: await response.json(),
      } as TError;
    } catch (e) {
      error = {
        status: "unknown" as const,
        payload:
          e instanceof Error
            ? `Unexpected error (${e.message})`
            : "Unexpected error",
      };
    }
    throw error;
  }

Where to Pass Request Headers?

Hi nice project so far.

i was using restful-react and was able to add my jwt after sign in to the request header.
now i am a bit stuck as the new "getters" do not have an option for the header...

could you please help how to handle that with openapi-codegen?
image

image

Any plans for adding multipart/form-data support?

Right now I am trying the codegen on our big codebase (124 controllers and countless methods) and it works perfectly.

I think basic idea is to check if requestBody.content has 'multipart/form-data' key and generate mutations accordingly.
And possible {prefix}fetcher.ts changes are needed.

I will investigate more but just wanted to ask beforehand.

JSDocs for parameters are incorrect

Hi,

I have noticed that JSDoc comments for parameters are not actual JSDoc comments. (Line[104, 108, 114])

For example:

/**
* Set secrets for GitHub Actions.
*/
export type ActionsSecret = {
/*
* @format date-time
*/
created_at: string;
/*
* The name of the secret.
*
* @example SECRET_TOKEN
*/
name: string;
/*
* @format date-time
*/
updated_at: string;
};

they should start with /** not /*

I noticed this while I was testing https://github.com/fabien0102/ts-to-zod

Even in the tests, JSDoc comments start with /* [Line 145]

"/**
* Generated by @openapi-codegen
*
* @version 1.0.0
*/
/**
* A new pet.
*/
export type Pet = NewPet & {
/*
* @format int64
*/
id: number;
};

For a reference from https://github.com/fabien0102/zod-schema-from-stripe

https://github.com/fabien0102/zod-schema-from-stripe/blob/b3a3156f7ca54d4dc411dfa826dfc2d8cd4e5a0c/src/stripe/stripeSchemas.ts#L150-L156

Because of this fabien0102/ts-to-zod can't generate correct zod schemas. It is ignoring maxLength, etc.

Error to generate code

Getting this error if trying to generate code.

Steps to reproduce this error:
1- create react app ( Vite or CRA)
2- install package: npm i -D @openapi-codegen/{cli,typescript}
3- generate code: npx openapi-codegen init

I am missing something?

Thanks

image

React-query documentation

openapi-codegen relies on React Query, but there is no mention of this in the documentation. Initialy, when I was exploring this package, I had to do quite some digging to find out what setup was needed in relation to React Query. For example, this article does a great job at explaining this setup.

For newcomers it might be confusing that there are missing references to @tanstack/react-query when they initialy run the code generation. Even if they install the package, there's still configuration with QueryClient and QueryClientProvider. I propose to add these steps to the setup instructions in the docs.

Inconsistent camel casing of path params between schema/types and runtime code

The typescript plugins paramsToSchema function formats path param keys to camel case.

The context template does not do this, which for my path params containing a . caused the query to not update when that path param changed (since the param value was never getting inserted into the key, so the key did not change).

The fetcher template also does not do this, which for my path params caused them to not be inserted.

Post-generation, I was able to fix both of these issues:
Context:

const resolvePathParam = (key: string, pathParams: Record<string, string>) => {
  if (key.startsWith('{') && key.endsWith('}')) {
    return pathParams[camel(key.slice(1, -1))];
  }
  return key;
};

Fetcher:

return (
  url.replace(/\{[\w|.]*\}/g, (key) =>
    encodeURIComponent(pathParams[camel(key.slice(1, -1))])
  ) + query
);

(For fetcher I also found I needed to add encodeURIComponent as I had some path params with / characters in them)

I know in #17 there is discussion that all params should match the OpenAPI spec, in which case the paramsToSchema function should not be camel casing. That said, I do prefer the developer experience of having camel cased params.... but either way the schema and runtime code templates need to match to avoid errors.

Adjust readme Frontend side example?

When I generate typescript code with generateSchemaTypes and generateReactQueryComponents, I end up with four files:

  • ...Components
  • ...Contexts
  • ...Fetcher
  • ...Schemas

In the ...Components file, two functions are exported: fetch... and use...

In the readme frontend example I understand the use... function should be used, but looking at the code, I get a feeling I should use the fetch... function. Could you confirm the intended usage?

btw, really liking ths project, just asking as someone new to the setup.

Return type is unknown unless explicitly defined

We're facing some issues with hooks having an unknown return type unless explicitly typed:

const {data: locations} = useListLocations({});
// -> const locations: unknown

const {data: locations} = useListLocations<ListLocationsResponse>({});
// -> const locations: Location[] | undefined
// hardcoding the known type fixes the issue:
// export const useListLocations = <TData = ListLocationsResponse>(

export const useListLocations = <TData>(
  variables: ListLocationsVariables,
  options?: Omit<
    reactQuery.UseQueryOptions<
      ListLocationsResponse,
      ListLocationsError,
      TData
    >,
    "queryKey" | "queryFn"
  >
) => {
  const { fetcherOptions, queryOptions, queryKeyFn } =
    useOctopusContext(options);
  return reactQuery.useQuery<ListLocationsResponse, ListLocationsError, TData>(
    queryKeyFn({ path: "/locations", operationId: "listLocations", variables }),
    () => fetchListLocations({ ...fetcherOptions, ...variables }),
    {
      ...options,
      ...queryOptions,
    }
  );
};
export type Location = {
  /*
   * @format uuid
   */
  id: string;
};

I'm not sure how createTypeParameterDeclaration works, but might have to do with this line.

https://github.com/fabien0102/openapi-codegen/blob/main/plugins/typescript/src/generators/generateReactQueryComponents.ts#L465

I was under the assumption that the type could be inferred instead of having to type it explicitly. We're using latest version.

Add `camelizedQueryParams` option

The idea will be to camelized every queryParams in the generated components/fetchers to improve the DX.

To have this working, we also need to generate a bit of extra logic in the fetcher if camel(name) !== name

export const fetchGetBaseBranchStats = (variables: GetBaseBranchStatsVariables) =>
  xatabaseFetch<GetBaseBranchStatsResponse, undefined, {}, GetBaseBranchStatsQueryParams, GetBaseBranchStatsPathParams>(
    { url: '/db/{basebranchName}/stats', method: 'get', ...variables, queryParams: {/* map to original names */} }
  );

`openapi-codegen init`

Summary

We want to have an init command to help our users to bootstrap the configuration file.

DX

No existing configuration

$ openapi-codegen init
> what’s the name of your api?
> where do your openapi file is? ("github" | "local" | "url")
> what codegen template do you want? ("typescript-schema-only" | "typescript-restfulreact" | "typescript-reactquery")
> where do you want to generate the code? ("outDir")

Existing configuration

$ openapi-codegen init
> you already have an existing configuration ("add a configuration" | "override the existing one")
[go to the same steps as "No existing configuration"]

=> This will generate openapi-codegen.config.ts and show an example command to run the config

Improve type safety on error responses

Right now, fetchers do not have a way to include error responses types. Add them and make the default fetcher to type the union of them.

Also, create types for inlined responses.

Deal with circular ref

The usage of Record<> can generate some circular ref

image

Actual:

export type RecordColumn =
  | boolean
  | number
  | number
  | string
  | string[]
  | Record<string, RecordColumn>;

Expected:

export type RecordColumn =
  | boolean
  | number
  | number
  | string
  | string[]
  | { [key: string]: RecordColumn };

Idea: passing custom fetcher options from generated hooks

Hi,

Right now fetcher only can access fetcherOptions from the global context.

  fetcherOptions: {
    // don't show errors if the request fails
    silent?: boolean;

    // don't send authorization header
    public?: boolean;
  };

I was thinking of adding support for passing it from generated hooks.

The basic idea is to add an optional parameter to generated hooks.

variables: ....,
options?: ....,
> fetcherOptionsOverride?: {prefix}Context['fetcherOptions']

And spread the passed parameters into:

() => fetchUserQuery({ ...fetcherOptions, ...fetcherOptionsOverride, ...variables }),

Possible use cases:

  • Normally users will handle errors in their {prefix}Fetcher.ts
    • But in some cases, for some operations, users can want to handle errors silently for example.
    • So they can add a silent?: boolean param to fetcherOptions and pass that option to custom generated hook
const apiKeyQuery = useUserGetApiKeyQuery(
  { pathParams: { id: user.id } }, // <--- variables
  { enabled: Boolean(user.id), onSuccess: setApiKey }, // <--- options
  { silent: true } // <--- optional fetcherOptionsOverride (type-safe)
);
  • Passing custom headers to handle better window.fetch
    • Some endpoints can be public so no authorization header is needed

It is totally optional feature, but it will add more functionality and support more generic use cases I think.

What do you think?

operationFetcherFnName naming

Great library!
Is there any way to change the operationFetcherFnName?
Ideally I'd like to export getMyDataType as a fetch component and useGetMyDataType as a react-query component.

Example of using FetcherOptions (or something else) to set request URL?

Apologies if this is the wrong place to post, but I am having trouble understanding how I can set some variables in the request URL.

In my openapi spec file (https://github.com/p2-inc/phasetwo-docs/blob/master/openapi.yaml), I have a servers section that looks like this:

servers:
  - url: '{protocol}://{host}{port}/auth/realms'
    variables:
      host:
        enum:
          - app.phasetwo.io
          - localhost
        default: app.phasetwo.io
        description: API host
      port:
        enum:
          - ''
          - '8081'
        default: ''
        description: API port
      protocol:
        enum:
          - http
          - https
        default: https

In my orgsFetchers.ts, it creates a baseUrl template like this:

const baseUrl = '{protocol}://{host}{port}/auth/realms';

which looks like it gets rendered here:

    const response = await window.fetch(`${baseUrl}${resolveUrl(url, queryParams, pathParams)}`, {

I'm able to set pathParams like this:

  const { data, error, isLoading } = useGetOrganizations({pathParams: {realm:'self'}});

but I can't figure out how to set anything in the baseUrl. Furthermore, the URL that ends up getting called, uses the host of the app as a prefix:

http://localhost:9000/%7Bprotocol%7D://%7Bhost%7D%7Bport%7D/auth/realms/self/orgs

So, two questions:

  1. how to set the parameters in the baseUrl?
  2. how to have the fetcher use the baseUrl without the host of the app as the prefix?

Thanks!

Typescript Schema generator

Summary

Generate all shared components types ("parameters" | "responses" | "schemas" | "requestBodies")

API

type File = { name: string; body: string; }
type Config = {
  paramatersFileName?: string;
  responsesFileName?: string;
  schemasFileName?: string;
  requestBodiesFileName?: string;
}
type generateSchemaTypes = (context: Context, config: Config) => Promise<File[]> | File[]

`openapi-codegen gen` (file)

Summary

The CLI should be able to call config[namespace].to with the correct openAPIDocument from source: "file"

Enhancement: allow to return a response wrapper from a Fetcher function

Hi,

I would like to suggest yet another enhancement to the library.

Currently, both the fetch fn and hook expect to return TData which doesn't allow for returning a wrapper from the fetcher itself. While not always necessary, there are times where you might need to have additional data on the response (such as status code, even when there is no error at all).

I'm no expert on the matter nor do I know that much of the inner workings of the library, but I think this could be somehow solved by the following ways:

  1. Exporting a TDataWrapper (or any other name) which would be a generic type that would use TData and where you can add anything you require (if necessary, by default it could be just TDataWrapper = TData) and using it from the components file.

  2. Adding a ReturnType<typeof myFetchFn> return type to the hooks and fetch fn of the type of the Fetcher function itself (with TData as a generic type), which would probably involve less changes to current Fetcher codebase, if any. This might sound confusing at a first glance, but the following can be used a source for the same behaviour I'm talking about (taken from Orval, another open api code generator): https://github.com/anymaniax/orval/blob/master/samples/react-query/custom-client/src/api/endpoints/petstoreFromFileSpecWithTransformer.ts#L54

Cheers!

Documentation for generateFetchers configuration

Hello @fabien0102, really liking this library!

I noticed that there are three typescript code generation functions possible: generateSchemaTypes, generateReactQueryComponents and generateFetchers.

The two first ones have nice documentation in the cli readme.

How would an example generateFetchers configuration look like?

If you could add one here, I could add it to said readme with a PR later.

feature: option to generate indexes?

Right now the files are put in the outputDir, but no index.ts is created.

Should there be an option to just export everything from the index?

How to set a header value that might change

Hi all,

I'm having a problem similar to #74 where I am setting the authorization header like this:

const { data: activeOrg, refetch } = useGetOrganizationById({
    pathParams: { realm: 'self', orgId },
    headers: {
      Authorization: `Bearer ${access_token}`,
    },
  });

My problem is that the access_token may expire while the page is loaded and component rendered, which will cause an auth error from the server. Is there a better way to do this such that I can specify the header value at request time, so that I can always provide a valid token?

Thanks for any help!

Syntax error generating types when example includes `*/`

Hi, I've discovered a syntax error when attempting to generate types for an openapi spec containing "*/" in one of my examples.

The openapi.json contains a components.schema declaration (roughly) as follows:

      "CronTimingCreate": {
        "title": "CronTimingCreate",
        "required": [
          "type",
          "cron_expression"
        ],
        "type": "object",
        "properties": {
          "cron_expression": {
            "title": "Cron Expression",
            "type": "string",
            "description": "The string representing the timing's cron expression.",
            "format": "cron-string",
            "example": "*/5 * * * *"
          },
        },
        "additionalProperties": false,
        "description": "Cron timing schema for create requests."
      }

The issue seems to be stemming from the example, which contains */ -- which I believe is being detected as the end of the comment here:

.replace(/^( )*(\/\*)?\*?( *)/g, "") // Remove opening comment notations
.replace("*/", ""); // Remove closing comment notation

Ultimately, I get this output from npx openapi-codegen gen api:

Syntax Error: Unexpected token (468:8)
  466 |      * @format cron-string
  467 |      * @example 5 * * * *
> 468 |      */*/
      |        ^
  469 |     cron_expression: string;
  470 |     /*
  471 |      * The IANA time zone to use convert `time` in this timing.
  466 |      * @format cron-string
  467 |      * @example 5 * * * *
> 468 |      */*/
      |        ^
  469 |     cron_expression: string;
  470 |     /*
  471 |      * The IANA time zone to use convert `time` in this timing.

Select doesn't infer returned type

Example:

const { data, isLoading } = useUserGetAllQuery(
  {},
  {
    select: (data: UsersResponsePagination) => data,
  }
);
console.log('generateHook', data, isLoading);
//                          ^: UsersResponsePagination | UserResponseWithEntitiesWithoutPermissions[]
//                          ^: Expected: UsersResponsePagination


// example usage with plain react-query gets correct result
const { data: useQueryData, isLoading: useQueryLoading } = useQuery(
  ['users'],
  () => fetchUserGetAllQuery({}),
  { select: (data: UsersResponsePagination) => data }
);
console.log('plain useQuery', useQueryData, useQueryLoading);
//                            ^: UsersResponsePagination

Generated Hook:

export const useUserGetAllQuery = (
  variables: UserGetAllQueryVariables,
  options?: Omit<
    reactQuery.UseQueryOptions<
      Schemas.UserResponseWithEntitiesWithoutPermissions[] | Schemas.UsersResponsePagination,
      UserGetAllQueryError,
      Schemas.UserResponseWithEntitiesWithoutPermissions[] | Schemas.UsersResponsePagination
    >,
    'queryKey' | 'queryFn'
  >
) => {
  const { fetcherOptions, queryOptions, queryKeyFn } = useCrmContext(options);
  return reactQuery.useQuery<
    Schemas.UserResponseWithEntitiesWithoutPermissions[] | Schemas.UsersResponsePagination,
    UserGetAllQueryError,
    Schemas.UserResponseWithEntitiesWithoutPermissions[] | Schemas.UsersResponsePagination
  >(
    queryKeyFn({ path: '/api/user', operationId: 'userGetAllQuery', variables }),
    () => fetchUserGetAllQuery({ ...fetcherOptions, ...variables }),
    {
      ...options,
      ...queryOptions,
    }
  );
};

Generated Context:

import type { QueryKey, UseQueryOptions } from '@tanstack/react-query';
import { parseCookies } from 'nookies';

import { QueryOperation } from './crmComponents';

export type CrmContext = {
  fetcherOptions: {
    /**
     * Headers to inject in the fetcher
     */
    headers?: { authorization?: string; 'X-Company-Domain'?: string };
    /**
     * Query params to inject in the fetcher
     */
    queryParams?: { companyId?: number };
  };
  queryOptions: {
    /**
     * Set this to `false` to disable automatic refetching when the query mounts or changes query keys.
     * Defaults to `true`.
     */
    enabled?: boolean;
    select?: (data: any) => any; // ????
    keepPreviousData?: boolean;
  };
  /**
   * Query key manager.
   */
  queryKeyFn: (operation: QueryOperation) => QueryKey;
};

/**
 * Context injected into every react-query hook wrappers
 *
 * @param queryOptions options from the useQuery wrapper
 */
export function useCrmContext<
  TQueryFnData = unknown,
  TError = unknown,
  TData = TQueryFnData,
  TQueryKey extends QueryKey = QueryKey
>(
  queryOptions?: Omit<
    UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
    'queryKey' | 'queryFn'
  >
): CrmContext {
  const { accessToken } = parseCookies();

  return {
    fetcherOptions: {
      headers: {
        authorization: accessToken ? `Bearer ${accessToken}` : undefined,
      },
    },
    queryOptions: Object.assign(
      {
        keepPreviousData: true,
      },
      queryOptions
    ),
    queryKeyFn: operation => {
      const queryKey: unknown[] = hasPathParams(operation)
        ? operation.path
            .split('/')
            .filter(Boolean)
            .map(i => resolvePathParam(i, operation.variables.pathParams))
        : operation.path.split('/').filter(Boolean);

      if (hasQueryParams(operation)) {
        queryKey.push(operation.variables.queryParams);
      }

      if (hasBody(operation)) {
        queryKey.push(operation.variables.body);
      }

      return queryKey;
    },
  };
}

// Helpers
const resolvePathParam = (key: string, pathParams: Record<string, string>) => {
  if (key.startsWith('{') && key.endsWith('}')) {
    return pathParams[key.slice(1, -1)];
  }
  return key;
};

const hasPathParams = (
  operation: QueryOperation
): operation is QueryOperation & {
  variables: { pathParams: Record<string, string> };
} => {
  return Boolean((operation.variables as any).pathParams);
};

const hasBody = (
  operation: QueryOperation
): operation is QueryOperation & {
  variables: { body: Record<string, unknown> };
} => {
  return Boolean((operation.variables as any).body);
};

const hasQueryParams = (
  operation: QueryOperation
): operation is QueryOperation & {
  variables: { queryParams: Record<string, unknown> };
} => {
  return Boolean((operation.variables as any).queryParams);
};

Binding element 'signal' implicitly has an 'any' type

In the generated code for components, I'm running into a problem due to our TypeScript configuration disallowing implicit any.

The error I get when running tsc as part of our build process:

src/generated/user/userComponents.ts:993:8 - error TS7031: Binding element 'signal' implicitly has an 'any' type.

993     ({ signal }) => fetchGetCurrentUser({ ...fetcherOptions, ...variables }, signal),

It looks like this single line could be fixed by setting a type on the signal property like:

({ signal }: { signal?: AbortSignal }) =>
      fetchGetCurrentUser({ ...fetcherOptions, ...variables }, signal),

Either that or defining a type like type SignalOption = { signal?: AbortSignal } and using that there.

Maybe there's a better way to do it to have the compiler infer the type for signal.

It would be nice to have the type explicitly defined to avoid this problem.

generateSchemaTypes: Take OpenAPI spec "servers" into account?

Quick question.

If during the code generation, one might want to specify a "baseURL" for the generated components, is it possble?

Taking the petStore as an example:

openapi: "3.0.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
servers:
  - url: http://petstore.swagger.io/v1
paths:
  /pets:
    get:
      summary: List all pets
      operationId: listPets
      tags:
        - pets
      parameters:
        - name: limit
          in: query
          description: How many items to return at one time (max 100)
          required: false
          schema:
            type: integer
            format: int32
      responses:
        '200':
          description: A paged array of pets
          headers:
            x-next:
              description: A link to the next page of responses
              schema:
                type: string
          content:
            application/json:    
              schema:
                $ref: "#/components/schemas/Pets"

Using this, then the goal would be to get the hook to use http://petstore.swagger.io/v1/pets instead of just /pets for its call.

One idea is to basically take context.openAPIDocument.servers[0].url and uses it as a prefix/base URL for all requests.

Add logo and badges

We need a nice logo and the classic badges (codecov, npm, …) on the Readme.md

Config to change pattern of hook name

Great project! It's a huge time safer.

Is there a way to change the pattern of how the hook name is generated?
I have the convention of adding Query or Mutation at the end of the hook name.

useSearchUsers -> useSearchUsersQuery
useGistsCreateComment -> useGistsCreateCommentMutation

No hooks/queries generated in xComponents.ts

Hi,

I went through the github example here and then tried it on my own openapi spec file here. However, there was nothing generated in the xComponents.ts file for using the methods/paths in the openapi spec, only:

import * as reactQuery from 'react-query';
import { usePhasetwoContext, PhasetwoContext } from './phasetwoContext';
import type * as Fetcher from './phasetwoFetcher';
import { phasetwoFetch } from './phasetwoFetcher';

export type QueryOperation = {
  path: string;
  operationId: never;
  variables: unknown;
};

I used the library like this:

$ npm i react-query
$ npm i -D @openapi-codegen/{cli,typescript}
$ npx openapi-codegen init
? Select the source of your OpenAPI Url
? Url https://raw.githubusercontent.com/p2-inc/phasetwo-docs/master/openapi.yaml
? What namespace do you want for your API? phasetwo
? What do you want to generate? React Query components
? Which folder do you want to generate? src/phasetwo
$ npx openapi-codegen gen phasetwo

Am I doing something wrong? Is there something unexpected about my openapi spec? It works with all my other tools.

Thanks for the help!

[Bug] - Fetcher tries to get data from frontend url instead of backend one.

Hi all! Thanks for making this package available, it looks amazing.

I'm just running into an issue that prevents me from using it..

I have followed up the configuration in the docs but i get the following issue:

here is my configuration file:

import {
  generateSchemaTypes,
  generateReactQueryComponents,
} from "@openapi-codegen/typescript";
import { defineConfig } from "@openapi-codegen/cli";

export default defineConfig({
  reviews: {
    from: {
      source: "url",
      url: "https://api.apis.guru/v2/specs/github.com/1.1.4/openapi.yaml",
    },
    outputDir: "generated",
    to: async (context) => {
      const filenamePrefix = "reviews";
      const { schemasFiles } = await generateSchemaTypes(context, {
        filenamePrefix,
      });
      await generateReactQueryComponents(context, {
        filenamePrefix,
        schemasFiles,
      });
    },
  },
});

Here we can see that types and everything get generated fine.

Screenshot 2022-07-26 at 13 21 45

this is my query on React frontend:

Screenshot 2022-07-26 at 13 22 33

and this is the error i encounter on frontend.

Screenshot 2022-07-26 at 13 23 15

i dont know why it is trying to fetch from localhost:3000 (which is where im running my React app on).

I really hope someone can support me on this, would really appreciate :)

handling multiple openapi .yaml files

Hi there,

this works great and i really like the DX it provides. I was wondering how would you handle multiple openapi source files? say your api is split in more than one .yaml file?

do i need to create multiple openapi-codegen.config.ts files and pass --config [name-of-config-file] to the npx openapi-codegen gen lmwr command? or is there another way i should go about this. i don't want to overwrite the fetcher and context files.

cheers,
C

Prompt & Task manager

Summary

In order to have the best DX as possible, we need to be able to display nice prompt to ask question and having a task manager to display progress during the generation

What are we looking for?

  • The library need to be typesafe
  • Nice output (colors, keyboard interactions)
  • A way to navigate into file system would be amazing
  • A way to navigate into github repo would be even more amazing

Libraries to explore

Prompt

https://github.com/goliney/garson -> No types exported, and the types are very bad

Task manager / Progress

https://github.com/privatenumber/tasuku
https://github.com/agracio/ts-progress

Going deeper (if previous libraries are not good enough)

https://github.com/vadimdemedes/ink
https://github.com/vadimdemedes/ink-testing-library

https://github.com/yarnpkg/berry/tree/master/packages/yarnpkg-libui

DX ref

https://cliffy.io/prompt/index.html

How to access headers from fetch functions?

For authentication in my application, I use a single endpoint for retrieving a CSRF token. This returns a new CSRF token inside the header X-CSRFToken . I need to extract this header from the response and store it inside memory.
Prior to the openapi-codegen, it was possible to it like so:

authGetCsrf()
  .then((response) => {
    const csrfToken = response.headers["x-csrftoken"];
    setCsrf(csrfToken);
  })

Now I can't do this anymore because the response object is missing. I can access it inside the fetcher.ts:

  [...]
  if (response.headers.has("x-csrftoken")) {
    console.log(`TOKEN: ${response.headers.get("x-csrftoken")}`)
  }
  [...]

But it seems like this doesn't help with the problem because I would then need it to store within my Auth context and I can't use hooks inside the fetcher.ts only inside the context.ts.
Is there anything I can do about it?

Possible workarounds could be:

  • Use an extra fetch function besides the generated client
  • Use an extra fetch function inside the context.ts not the generated one
  • Return the CSRF token inside the body

but really only if it isn't possible

Question: extending context initial request headers

Hi everyone,

I have a question, I believe it's currently not possible but if it is, it would be nice to know.

With the fetcherOptions in the apiContext (auto-generated and manually modified) I am able to set some specific headers all of the requests. Let's say, for example, the authentication header which will be common to all requests (ie. Bearer Auth).

There are certain APIs, however, that require their own headers. In those cases, with the parameters of the use* generated function I'm able to send those as well without problem.

However, the problem that happens is that because of the way the fetch fn is called, all these initial fetcherOptions headers seem to get overwritten:

myFetchFn({ ...fetcherOptions, ...variables }, signal),

Is there any way to allow it overwrite in a property-specific way? For example something like this:

{headers: {...fetcherOptions.headers, ...variables.headers}} (I know we should check for possible null objects, I'm omitting it for simplicity's sake).

Other way I've been thinking of that could be possible to implement something like this would be passing the variables to the useApiContext() call at the start of each use* function and letting each person to extend the resulting variables in their own fashion or requirements per as the project.

What do you think? Thank you for your help

`openapi-codegen gen` (github)

Summary

The CLI should be able to call config[namespace].to with the correct openAPIDocument from source: "github"

To be handled

  • If the github repo is private, we need to ask a token
  • If the auth fail, we need to re-ask for a token
  • we should be able to add --branch, --owner, --repository, --path to override an existing config

Deploy process

In order to have a smooth npm publishing flow we need to:

  • Add lerna
  • Add release-please bot

The wanted flow is to have a pull-request to merge from release-please bot to bump all packages versions.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.