docusign / OpenAPI-Specifications

The official Docusign REST APIs Swagger Specifications
MIT License
20 stars 18 forks source link

Why you be like this bro? #109

Closed Arlen22 closed 1 month ago

Arlen22 commented 1 month ago

My history of working with Docusign APIs in Typescript goes like this:

Yay! They have a node SDK. Why doesn't my DocuSign library work with webpack? Oh well, my project is only partly webpack, so just install it as an external dependency and it works fine. Now let me figure out how to get a jwt user token and make a client request.

Wait, why do they rename the response property? Now I have to override the response object. And I also have to inspect the options manually because the definitely typed library doesn't include the request and response types for random parameters.

import * as docusign from 'docusign-esign';
/** DOCUSIGN!!! WHY??? */
interface DocusignResponseHORROR<T> extends AxiosResponse<T> {
  data: never;
  body: AxiosResponse<T>["data"];
}
const client = new docusign.ApiClient();
const token: DocusignResponseHORROR<TokenInfo> = await client.requestJWTUserToken(...);
client.setBasePath(this.env.DOCUSIGN_BASEPATH);
client.addDefaultHeader('Authorization', 'Bearer ' + token.body.access_token);
const result = await new docusign.EnvelopesApi(client).createEnvelope(accountID, {
  // inspect EnvelopesAPI source code to determine parameters
});

Seriously? Why do I have to constantly inspect source code? Especially when I have to navigate to the source code manually because I'm using @types/docusign-esign and can't just ctrl-click? Wait a minute, what's this about it being manually generated? Oh, it's based on OpenAPI... and... I can generate it myself? Let me do that! Surely then none of this weirdness will happen.

Oh nice, I found @hey-api/openapi-ts.

const res = await Envelopes_PostEnvelopes({
  baseUrl: this.env.DOCUSIGN_BASEPATH,
  headers: { 'Authorization': 'Bearer ' + await this.getDocusignToken() },
  path: { accountId: this.env.DOCUSIGN_JWT_AccountId },
  query: { },
  body: envelope,
});

Wait, why am I getting type errors? After more digging through source code that I had to manually navigate, I figured out that the builder was trying to declare types verbatim without checking their type names and thus trying to declare primitive types. So I make a custom generator file which renames all the schema definitions.

import { createClient } from '@hey-api/openapi-ts';
import { resolve } from 'path';
import { existsSync,readFileSync } from "fs";
const file = "/root/docusign/OpenAPI-Specifications/esignature.rest.swagger-v2.1.json";
if (!existsSync(file)) {
  throw new Error(`File not found: ${file}`);
}

// DOCUSIGN!!! SERIOUSLY!!!
// because the definitions are directly built as types and because you can't override primitive types (`export type number = ...`), I have to rename the definitions.
const input = JSON.parse(readFileSync(file, 'utf8'), (key, value) => {
  if (key === "$ref" && value.startsWith("#/definitions/")) {
    return value.replace("#/definitions/", "#/definitions/def_");
  } else {
    return value;
  }
});
Object.entries(input.definitions).forEach(([key, value]) => {
  input.definitions[`def_${key}`] = value;
  delete input.definitions[key];
});

createClient({
  input,
  output: process.argv[2] || "docusign-client",
  client: '@hey-api/client-fetch',
  services: {
    methodNameBuilder(operation) {
      console.log(operation["x-ds-methodname"] || operation.id);
      return operation["x-ds-methodname"] || operation.id;
    },
  },
});

Great! Now I have a properly working third party library to access the DocuSign API.

Does everyone do this?

Arlen22 commented 1 month ago

This should go in the node repo