hey-api / openapi-ts

🚀 The OpenAPI to TypeScript codegen. Generate clients, SDKs, validators, and more. Support: @mrlubos
https://heyapi.dev
Other
1.27k stars 100 forks source link

Add mechanism to easily access endpoint urls #452

Open mci-kmd opened 6 months ago

mci-kmd commented 6 months ago

Description

Before I started using this library I would have an object defined like below, for accessing the urls. The primary use case was to use it with the http client, which has become irrelevant when using the generated services. However, there are other cases where I would like to have access to this in relation to testing. In my Cypress tests, for instance, I override requests by specifying the requested url. There does not seem to be a way to actually access the url in the generated code, so I would need to maintain something like the below in addition to the generated code.

So, the feature request is for the generated code to export an object like this, which it can then use internally in the services as well (or not - it does not really matter except for the size of the generated output).

export const apiEndpoints = {
  users: {
    permissions: {
      areaAccess: '/api/v1.0/users/permissions/area-access',
    },
  },
  management: {
    organizations: {
      getOrganizations: '/api/v1.0/management/organizations',
      getOrganizationHierarchy: (resourceId: string, reourceType: ResourceType) =>
        `/api/v1.0/management/organizations/${resourceId}/${reourceType}/hierarchy`,
    },
    departments: {
      getDepartmentContacts: (departmentId: string) => `/api/v1.0/management/departments/${departmentId}/contacts`,
    },
  },
};
mrlubos commented 6 months ago

Hey @mci-kmd, can you provide some code for your use case? It's a bit hard to understand how you're using paths today in tests. Thanks!

mci-kmd commented 6 months ago

@mrlubos Sure thing.

Here is one of my Cypress tests:

it('The contacts list has a loading indicator while waiting for data from the server', () => {
  cy.intercept('GET', apiEndpoints.management.departments.getDepartmentContacts(department.id), {
    statusCode: 200,
    body: [],
    delay: 10000, // Forever
  });
  cy.visit(departmentContactsUrl(department.id));
  cy.get('body').should('not.contain', 'Loading...');
  cy.get('department-contacts-page').find('loading-indicator');
});

Another case is in Storybook, where we configure a fake http client with responses for a number of endpoints:

{
  provide: HttpClient,
  useFactory: () =>
    new FakeHttpClient({
      [apiEndpoints.management.organizations.getOrganizations]: () => [ORGANIZATION],
      [apiEndpoints.management.organizations.getOrganizationHierarchy(ORGANIZATION.id, 'Organization')]: () =>
        ORGANIZATION_HIERARCHY,
      [apiEndpoints.management.organizations.getOrganizationHierarchy(DEPARTMENTS[0].id, 'Department')]: () =>
        ORGANIZATION_HIERARCHY,
    }),
},
mrlubos commented 5 months ago

@mci-kmd as a workaround, can you mock the service layer in your tests?

mci-kmd commented 5 months ago

@mrlubos I'm not really interested in mocking anything at the framework level (Angular in this case) since I like the simplicity of the outside-in approach I've been using with Cypress. My current workaround is just to maintain the object described in the issue. It is not a huge burden to have this duplicate code, at this point in the project at least, so consider this more of a suggestion for a quality of life upgrade. If you deem my use case to niche then I'm fine with that.

mrlubos commented 5 months ago

I'm just thinking how I could integrate this into any of the existing features. It doesn't really fit anywhere, this feels more like a part of SDK. With the new clients, services will be flattened into separate functions, so it wouldn't make sense to pull a huge URL tree in there. If we generated a whole SDK however, maybe it could be a part of that? Or we'd need a separate layer for testing... is the tree the only desired shape? Would you also be okay with say importing individual functions? For example, instead of apiEndpoints.management.departments.getDepartmentContacts(department.id), it would be getDepartmentContactsUrl(department.id)?

mci-kmd commented 5 months ago

The tree shape is not essential, any way to get the url would be fine as long as it is clear which url you are getting.

AndreasJJ commented 2 months ago

Hello, just wanted to chime in that getting the endpoint url would be helpful when generating a client for React Native /w Expo in certain circumstances. React Native's implementation for the Fetch Api is pretty lackluster, where you have to use a polyfill https://github.com/react-native-community/fetch to make streaming work. This polyfill isn't great either in my opinion and is rather slow compared to the native fetch in browsers. That's why we went with Expo's Filesystem (expo-file-system) package for downloading files instead, which would look something like the code snippet below.

const url = new URL(
        `${process.env.EXPO_PUBLIC_API_URL}/api/some-endpoint/${someParam}/file`
      );
      url.searchParams.set('a', foo);
      url.searchParams.set('b', bar);
      const resumable = FileSystem.createDownloadResumable(url.href, filePathURI, {
        headers: {
          Authorization: `Bearer ${await accessToken()}`
        }
      });
      const result = await resumable.downloadAsync();

It would be preferable to have a function or some way to get the url from the auto generated code, rather than having to manually create the url ourself. Although, it's not a huge problem for us as there's a very limited amount of endpoints that return binary data.

jpenna commented 1 month ago

It would make it easier to bypass some limitations of the lib while it's being worked uppon.

For example https://github.com/hey-api/openapi-ts/issues/859: if I could get the URL, I could at least make it work easier.