Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"extends": "standard",
"plugins": [
"standard",
"import",
"n",
"promise",
"chai-friendly"
],
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -54,7 +54,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v3

# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
Expand All @@ -68,4 +68,4 @@ jobs:
# make release

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v3
32 changes: 16 additions & 16 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ name: CI
on: [ push, pull_request ]

env:
DEFAULT_NODE: '16'
DEFAULT_NODE: '24'

jobs:
lockfile-lint:
name: Lockfile lint
runs-on: 'ubuntu-latest'
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v5
- uses: actions/setup-node@v6
with:
node-version: ${{ env.DEFAULT_NODE }}
- name: lint lock file
Expand All @@ -21,22 +21,22 @@ jobs:
strategy:
matrix:
platform: [ ubuntu-latest ]
node: [ '10', '12', '14', '16' ]
node: [ '24' ]
name: Unit Tests Node ${{ matrix.node }} (${{ matrix.platform }})
needs: lockfile-lint
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v5
- uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node }}
- name: install dependencies
run: npm ci
run: npm ci --ignore-scripts
- name: unit tests
run: npm run test:coverage
- name: upload code coverage artifacts
if: ${{ matrix.node == env.DEFAULT_NODE }}
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v6
with:
name: coverage
path: coverage
Expand All @@ -45,31 +45,31 @@ jobs:
needs: [test, lockfile-lint]
runs-on: 'ubuntu-latest'
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v5
- uses: actions/setup-node@v6
with:
node-version: ${{ env.DEFAULT_NODE }}
- name: install dependencies
run: npm ci
run: npm ci --ignore-scripts
- name: download code coverage artifacts
uses: actions/download-artifact@v2
uses: actions/download-artifact@v7
with:
name: coverage
path: coverage
- name: publish coverage to coveralls
uses: coverallsapp/github-action@master
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

lint:
needs: lockfile-lint
runs-on: 'ubuntu-latest'
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v5
- uses: actions/setup-node@v6
with:
node-version: ${{ env.DEFAULT_NODE }}
- name: install dependencies
run: npm ci
run: npm ci --ignore-scripts
- name: lint
run: npm run lint
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ jobs:
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v5
- uses: actions/setup-node@v6
with:
node-version: '14'
node-version: '24'
- name: install dependencies
run: npm ci --ignore-scripts
- name: release
Expand Down
60 changes: 60 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,65 @@
# Master

# 3.0.0-rc.1 - 19 June, 2026
### Breaking changes
- **Upgraded ajv from v6 to v8** — error messages changed from "should" to "must" (see [MIGRATION.md](./MIGRATION.md) for full mapping)
- **Validation error ordering** may differ from v2 (e.g., `required` errors may appear before `additionalProperties`)
- **Custom keywords** (`ajv-keywords` v5): `schemaPath` and `params` format changed for sub-keyword errors
- Upgraded eslint from v8 to v10 requiring new flat config format
- Dropped Node.js support below version 24
- Removed eslint-config-standard (incompatible with eslint v10)
- Removed eslint-plugin-node, eslint-plugin-standard, and eslint-plugin-import
- Updated minimum Node.js version from ">=8" to ">=24"

### New features
- **OpenAPI 3.0 `nullable` support for Ajv v8** — `nullable: true` is automatically converted to `type: [T, "null"]`
- **`instancePath` → `dataPath` normalization** — Ajv v8's `instancePath` (JSON Pointer format) is automatically converted to dot-notation `dataPath` for backward compatibility

### Dependencies
- ajv: ^6.12.6 → ^8.20.0
- Added ajv-formats: ^3.0.1 for ajv v8 format validators
- decimal.js: ^10.3.1 → ^10.6.0
- js-yaml: ^3.14.1 → ^4.2.0
- openapi-schema-validator: ^3.0.3 → ^12.1.3
- ajv-keywords: ^3.5.2 → ^5.1.0
- chai: ^4.3.4 → ^4.5.0
- chai-as-promised: ^7.1.1 → ^7.1.2
- eslint: ^8.5.0 → ^10.5.0
- Removed eslint-plugin-import (incompatible with eslint v10)
- eslint-plugin-n: ^11.1.0 → ^16.6.2
- eslint-plugin-promise: ^4.3.1 → ^6.6.0
- eslint-plugin-chai-friendly: ^0.6.0 → ^1.2.1
- mocha: ^8.4.0 → ^11.7.6
- nyc: ^15.1.0 → ^18.0.0
- snyk: ^1.812.0 → ^1.1305.1
- uuid: ^8.3.2 → ^14.0.0

### Code changes
- Added `normalizeAjvErrors` utility to convert Ajv v8 errors (`instancePath`) to v6-compatible format (`dataPath`)
- Added `convertNullable` to transform OpenAPI 3.0 `nullable: true` to Ajv v8-compatible `type` arrays
- Updated `addKeyword` calls to use Ajv v8 API format (`{ keyword, ...definition }`)
- Added ajv-formats support for ajv v8 format validators
- Updated all Ajv instances to use `addFormats(ajv)` and `strict: false` for v8 compatibility
- Updated ajv-keywords import paths for v5 compatibility
- Removed deprecated `nullable` option from Ajv configurations
- Updated test assertions for Ajv v8 error message format and ordering changes
- Created eslint.config.js for ESLint v10 flat config format
- Excluded eslint.config.js from nyc coverage reporting

### Migration
See [MIGRATION.md](./MIGRATION.md) for a detailed migration guide including a full error message mapping table.

### CI/CD
- Updated GitHub Actions to use latest action versions
- Updated Node.js test matrix to [24]
- Updated default Node.js version to 24
- Updated actions/checkout@v4 → @v5
- Updated actions/setup-node@v4 → @v6
- Updated actions/upload-artifact@v4 → @v6
- Updated actions/download-artifact@v4 → @v7
- Updated github/codeql-action/*@v1 → @v3
- Updated coverallsapp/github-action@master → @v2

# 2.0.5 - 1 February, 2021
### Improvements
- Added basic support for relative URLs #59
Expand Down
170 changes: 170 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# Migration Guide: v2.x → v3.x

This document describes all breaking changes introduced in `api-schema-builder` v3.0.0 and provides guidance on how to update your code.

## Why v3?

The core validation engine was upgraded from **Ajv v6** to **Ajv v8**. This is a major upgrade that brings performance improvements, better JSON Schema support, and stricter validation — but it also changes the format of validation error objects.

Additionally, all other dependencies have been bumped to their latest versions:
- `ajv`: `^6.12.6` → `^8.20.0`
- `ajv-formats`: (new) `^3.0.1` — formats are now a separate package in Ajv v8
- `ajv-keywords`: `^3.5.2` → `^5.1.0`
- `openapi-schema-validator`: `^3.0.3` → `^12.1.3`
- `js-yaml`: `^3.14.1` → `^4.2.0`
- `mocha`: `^8.4.0` → `^11.7.6`
- And others (see `package.json` for full list)

**Minimum Node.js version**: `>=24` (was `>=10`)

---

## Breaking Changes

### 1. Validation Error Messages

Ajv v8 changed all error messages from **"should"** to **"must"**. If your code inspects `error.message` strings from validation results, you will need to update your string comparisons.

#### Message Mapping

| v2 (Ajv v6) | v3 (Ajv v8) |
|---|---|
| `should be string` | `must be string` |
| `should be number` | `must be number` |
| `should be integer` | `must be integer` |
| `should be boolean` | `must be boolean` |
| `should be object` | `must be object` |
| `should be array` | `must be array` |
| `should be <= N` | `must be <= N` |
| `should be >= N` | `must be >= N` |
| `should have required property 'X'` | `must have required property 'X'` |
| `should match pattern "X"` | `must match pattern "X"` |
| `should match format "X"` | `must match format "X"` |
| `should match some schema in anyOf` | `must match a schema in anyOf` |
| `should match exactly one schema in oneOf` | `must match exactly one schema in oneOf` |
| `should be equal to one of the allowed values` | `must be equal to one of the allowed values` |
| `should NOT be shorter than N characters` | `must NOT have fewer than N characters` |
| `should NOT be valid` | `must NOT be valid` |
| `should NOT have additional properties` | `must NOT have additional properties` |
| `should pass "X" keyword validation` | `must pass "X" keyword validation` |

#### Example

```js
// v2
if (error.message === 'should be string') { ... }

// v3
if (error.message === 'must be string') { ... }
```

#### Quick Migration

If you have many string comparisons, a simple find-and-replace approach works:

```
"should be string" → "must be string"
"should have required" → "must have required"
"should match pattern" → "must match pattern"
"should match format" → "must match format"
"should NOT" → "must NOT"
"should pass" → "must pass"
"should match some schema in anyOf" → "must match a schema in anyOf"
"should match exactly one schema in oneOf" → "must match exactly one schema in oneOf"
```

> **Note**: The `anyOf` message also changed wording: "should match **some** schema" → "must match **a** schema"

---

### 2. Error Object Structure

The `dataPath` field is preserved on all errors produced by this library's validators (request, response, body, headers). However, the underlying Ajv v8 error objects no longer include `dataPath` natively — they use `instancePath` instead.

This library converts `instancePath` back to `dataPath` for backward compatibility with the same dot-notation format (e.g., `.body.name`, `.headers['x-zooz-request-id']`).

**What stays the same:**
- `error.dataPath` — still uses dot notation (`.body`, `.body.name`, `.query`, `.path`)
- `error.keyword` — unchanged
- `error.params` — unchanged (with minor exceptions, see below)
- `error.schemaPath` — unchanged for standard keywords

**What may differ:**
- Errors from `openapi-schema-validator` (OpenAPI spec validation errors thrown during schema loading) will have `instancePath` instead of `dataPath`, since they come directly from the external library.

---

### 3. Error Ordering

Ajv v8 may produce validation errors in a **different order** than Ajv v6. For example:

```
// v2: additionalProperties error comes first, then required
// v3: required error comes first, then additionalProperties
```

If your code relies on the specific ordering of errors in the `errors` array, you should update it to be order-independent or sort errors before comparing.

---

### 4. Custom Keywords (`ajv-keywords`)

If you use custom keywords via the `keywords` option (e.g., `range`, `prohibited`):

- **`schemaPath`** format changed. Sub-keyword errors now include the parent keyword in the path:
- v2: `#/properties/age/maximum` → v3: `#/properties/age/range/maximum`
- v2: `#/not` → v3: `#/prohibited/not`

- **`params`** for custom keyword wrapper errors no longer include `keyword`:
- v2: `{ keyword: 'range' }` → v3: `{}`

- **Import paths** for `ajv-keywords` changed:
- v2: `require('ajv-keywords/keywords/range')`
- v3: `require('ajv-keywords/dist/keywords/range')`

---

### 5. OpenAPI 3.0 `nullable` Support

In Ajv v8, the `nullable` keyword from OpenAPI 3.0 is no longer natively supported. This library now automatically converts `nullable: true` to the equivalent JSON Schema representation:

- `{ type: "string", nullable: true }` → `{ type: ["string", "null"] }`
- `{ nullable: true }` (without type) → `{ oneOf: [{...}, { type: "null" }] }`

This should be transparent to users — nullable fields will continue to accept `null` values as expected.

---

### 6. `addKeyword` API Change

If you register custom Ajv keywords through the `keywords` option using `{ name, definition }` objects, the library now passes them to Ajv v8 using the new API format:

```js
// v2 (Ajv v6): ajv.addKeyword(name, definition)
// v3 (Ajv v8): ajv.addKeyword({ keyword: name, ...definition })
```

This is handled internally — no changes needed in your code unless you are passing raw Ajv keyword definitions that rely on the old parameter format.

---

### 7. Strict Mode

Ajv v8 is more strict by default. The library sets `strict: false` to maintain compatibility with OpenAPI schemas that may use non-standard keywords. If you pass custom `ajvConfigBody` or `ajvConfigParams` options, be aware that `strict: true` may cause failures with OpenAPI-specific keywords.

---

## How to Migrate

1. **Update your dependency**: `npm install api-schema-builder@3`
2. **Search your codebase** for any string comparisons against validation error messages (see the mapping table above)
3. **Update error message assertions** in your tests from "should" to "must"
4. **Check error ordering** — if you compare full error arrays with deep equality, the order may have changed
5. **Update `ajv-keywords` imports** if you use them directly: `keywords/` → `dist/keywords/`
6. **Ensure Node.js >= 24**

---

## Need Help?

If you encounter issues during migration, please [open an issue](https://github.com/PayU/api-schema-builder/issues) with details about the error and your schema.
Loading