Repository: Redocly/redocly-cli
Pull request #1792 (2024-10-31 14:16:18)

fix: do not fail when bundling a description containing null in examples
## What/Why/How?

Fixed API descriptions bundling. Previously, schemas containing nulls in examples were causing failures.

## Reference

The original bug was introduced [here](https://github.com/Redocly/redocly-cli/pull/1747).

## Testing

Discovered the bug while testing the OpenAPI VSCode extension:

```yaml
        openapi: 3.1.0
        paths: 
          /:
            get: 
              responses: 
                200:
                  content: 
                    application/json: 
                      examples: 
                        Foo:   # <-- if fails when trying to resolve externalValues here
```

## Screenshots (optional)

## Check yourself

- [x] Code changed? - Tested with redoc/reference-docs/workflows (internal)
- [x] All new/updated code is covered with tests
- [ ] New package installed? - Tested in different environments (browser/node)

## Security

- [x] Security impact of change has been considered
- [x] Code follows company security practices and guidelines


changeset-bot[bot]: ###  🦋  Changeset detected

Latest commit: cac939faec83a2b5371c2c195e0efc0cced6057b

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 2 packages</summary>

  | Name                  | Type  |
| --------------------- | ----- |
| @redocly/openapi-core | Patch |
| @redocly/cli          | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/Redocly/redocly-cli/new/fix/bundle-failing-on-null-examples?filename=.changeset/fuzzy-toes-perform.md&value=---%0A%22%40redocly%2Fopenapi-core%22%3A%20patch%0A---%0A%0Afix%3A%20do%20not%20fail%20when%20bundling%20a%20description%20containing%20null%20in%20examples%0A)


github-actions[bot]: | Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `redocly lint packages/core/src/benchmark/benches/rebilly.yaml` | 963.7 ± 20.5 | 944.7 | 1016.3 | 1.00 |
| `redocly-next lint packages/core/src/benchmark/benches/rebilly.yaml` | 967.9 ± 26.2 | 947.1 | 1027.7 | 1.00 ± 0.03 |

<!-- thollander/actions-comment-pull-request "latest-vs-next-comparison" -->
github-actions[bot]: <!-- jest coverage report action for options with hash 3339ae53a38c44382c0fb4491bdb282d -->

## Coverage report 



| <div title="Status of coverage:&#10; 🟢 - ok&#10; 🟡 - slightly more than threshold&#10; 🔴 - under the threshold">St.<sup>:grey_question:</sup></div> | Category   | Percentage | Covered / Total |
| :----------------------------------------------------------------------------------------------------------------------------------------------------: | :--------- | :--------- | :-------------: |
|                                                                           🟡                                                                           | Statements | 78.83%     |    5016/6363    |
|                                                                           🟡                                                                           | Branches   | 67.53%     |    2076/3074    |
|                                                                           🟡                                                                           | Functions  | 73.32%     |     830/1132    |
|                                                                           🟡                                                                           | Lines      | 79.14%     |    4734/5982    |


## Test suite run success
812 tests passing in 121 suites.

<p align="right">Report generated by <a href="https://github.com/ArtiomTr/jest-coverage-report-action">🧪jest coverage report action</a> from cac939faec83a2b5371c2c195e0efc0cced6057b</p>


Patch:
diff --git a/.changeset/slimy-humans-nail.md b/.changeset/slimy-humans-nail.md
new file mode 100644
index 00000000..855309d5
--- /dev/null
+++ b/.changeset/slimy-humans-nail.md
@@ -0,0 +1,6 @@
+---
+"@redocly/openapi-core": patch
+"@redocly/cli": patch
+---
+
+Fixed API descriptions bundling. Previously, schemas containing nulls in examples were causing failures.
diff --git a/packages/core/src/bundle.ts b/packages/core/src/bundle.ts
index 4c2deaf2..cf7af97e 100755
--- a/packages/core/src/bundle.ts
+++ b/packages/core/src/bundle.ts
@@ -9,7 +9,7 @@ import {
   SpecMajorVersion,
   SpecVersion,
 } from './oas-types';
-import { isAbsoluteUrl, isRef, refBaseName } from './ref-utils';
+import { isAbsoluteUrl, isExternalValue, isRef, refBaseName } from './ref-utils';
 import { initRules } from './config/rules';
 import { reportUnresolvedRef } from './rules/no-unresolved-refs';
 import { dequal, isPlainObject, isTruthy } from './utils';
@@ -380,7 +380,7 @@ function makeBundleVisitor(
     },
     Example: {
       leave(node: any, ctx: UserContext) {
-        if (node.externalValue && node.value === undefined) {
+        if (isExternalValue(node) && node.value === undefined) {
           const resolved = ctx.resolve({ $ref: node.externalValue });
 
           if (!resolved.location || resolved.node === undefined) {


Test patch:
diff --git a/packages/core/src/__tests__/bundle.test.ts b/packages/core/src/__tests__/bundle.test.ts
index e5a7848c..fa9e4c26 100644
--- a/packages/core/src/__tests__/bundle.test.ts
+++ b/packages/core/src/__tests__/bundle.test.ts
@@ -265,6 +265,57 @@ describe('bundle', () => {
     expect(problems).toHaveLength(0);
     expect(res.parsed).toMatchSnapshot();
   });
+
+  it('should not fail when bundling openapi with nulls', async () => {
+    const testDocument = parseYamlToDocument(
+      outdent`
+        openapi: 3.1.0
+        paths: 
+          /:
+            get: 
+              responses: 
+                200:
+                  content: 
+                    application/json: 
+                      schema: 
+                        type: object
+                        properties: 
+                      examples: 
+                        Foo:           
+      `,
+      ''
+    );
+
+    const config = await makeConfig({ rules: {} });
+
+    const {
+      bundle: { parsed },
+      problems,
+    } = await bundleDocument({
+      document: testDocument,
+      config: config,
+      externalRefResolver: new BaseResolver(),
+    });
+
+    expect(problems).toHaveLength(0);
+    expect(parsed).toMatchInlineSnapshot(`
+      openapi: 3.1.0
+      paths:
+        /:
+          get:
+            responses:
+              '200':
+                content:
+                  application/json:
+                    schema:
+                      type: object
+                      properties: null
+                    examples:
+                      Foo: null
+      components: {}
+
+    `);
+  });
 });
 
 describe('bundleFromString', () => {