Skip to content

Transform nullable returns | null instead of optional ? #1411

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

Closed
2 tasks done
JorrinKievit opened this issue Oct 30, 2023 · 4 comments · Fixed by #1417
Closed
2 tasks done

Transform nullable returns | null instead of optional ? #1411

JorrinKievit opened this issue Oct 30, 2023 · 4 comments · Fixed by #1417
Labels
bug Something isn't working openapi-ts Relevant to the openapi-typescript library

Comments

@JorrinKievit
Copy link
Contributor

JorrinKievit commented Oct 30, 2023

Description

I'm following the example from https://openapi-ts.pages.dev/node/, but the type becomes file: Blob | null instead of file?: Blob. Maybe this is the desired result, in that case, how can I make it optional with the question mark notation? I tried appending undefined as well, but that results in file: Blob | undefined which is also not what I'm looking for 😄

Name Version
openapi-typescript 7.0.0-next.2
Node.js 18.x.x
OS + version Windows 11

Reproduction

import openapiTS from "openapi-typescript";
import ts from "typescript";

const BLOB = ts.factory.createIdentifier("Blob"); // `Blob`
const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull()); // `null`

const ast = await openapiTS(mySchema, {
  transform(schemaObject, metadata) {
    if (schemaObject.format === "binary") {
      return schemaObject.nullable
        ? ts.factory.createUnionTypeNode([BLOB, NULL])
        : BLOB;
    }
  },
});

Expected result
file?: Blob

Checklist

@JorrinKievit JorrinKievit added bug Something isn't working openapi-ts Relevant to the openapi-typescript library labels Oct 30, 2023
@drwpow
Copy link
Contributor

drwpow commented Oct 30, 2023

Ah that’s a really good question! You can’t. Because the way the TS AST works, the ? optional operator exists on the parent node you can’t access with transform().

For most cases, Blob | undefined will work the same way, however, there are some nuanced differences where ? is preferred, and if you fall into one of those edge cases, we’d have to change something to allow for that.

🤔 I wonder if to fix this, transform() allows an alternate syntax, e.g.:

const ast = await openapiTS(mySchema, {
  transform(schemaObject, metadata) {
    if (schemaObject.format === "binary") {
      return {
        schema: BLOB, // the root schema
        questionToken: true, // set `?`
        // some other options…
      };
    }
  },
});

This format would allow us to add additional options, if any, of which questionToken would enable the desired behavior (I know questionToken is a weird name but that’s what it’s referred to in TypeScript’s internal API 🤷)

@JorrinKievit
Copy link
Contributor Author

Yes I was thinking the same thing while messing around with the transform function! Those options would be great. I can try and create a starting point and hack something together in my spare time, but I have no experience with TS AST/TS compiler API. So there is a bit of a learning curve 😛

@drwpow
Copy link
Contributor

drwpow commented Oct 31, 2023

Yeah I’d welcome a PR if you have time and interest. This task is also fairly easy, and a good introduction to the TS AST.

The best way to start is TDD: first write a failing test that outlines the API you want, then just throw code around until it passes.

For this particular problem, I’d like to keep the original API in tact, but also accept this “expanded” syntax as a secondary format. I’ve seen this pattern in other libraries that let you omit metadata because it just cuts down on boilerplate for most people. But you should be able to get by with:

const result = options.ctx.transform(schemaObject, options);
if (result) {
  // expanded syntax
  if (typeof result === 'object' && result.schema) {
    // (new code)
  }
  // original syntax
  else {
    // (existing code)

Simply checking for .schema would be sufficient as I think that doesn’t appear anywhere in the TS API (much easier than trying to check the returned object is a valid TS node, of which there are many, many types).

The rest of the code should be pretty lightweight and straightforward; it’s just familiarizing yourself with the TS API and finding the right places to plug in. However, the one final “trick” is we may need to move the transform() call one level up out of the schema object code and into the parents of wherever that’s called so the questionToken gets applied. Ideally we only call transform() once per node for efficiency, but that’s minor cleanup that can even happen after the PR lands (note that code can be duplicated across multiple code paths if it has to, while still only being called once per node)

@JorrinKievit
Copy link
Contributor Author

Thanks for the detailed explanation! Will take a look when I got some spare time this week 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working openapi-ts Relevant to the openapi-typescript library
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants