Skip to content

Compare two Json based API documents (OpenAPI, AsyncAPI, JsonSchema, GraphAPI)

License

Notifications You must be signed in to change notification settings

gofynd/api-smart-diff

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

api-smart-diff

npm npm npm type definitions GitHub

This package provides utils to compute the diff between two Json based API documents - online demo

Purpose

  • Generate API changelog
  • Identify breaking changes
  • Ensure API versioning consistency

Supported API specifications

Features

  • Generate diff for supported specifications
  • Generate merged spec with changes in metadata
  • Classify all changes as breaking, non-breaking, annotation
  • Human-readable change description (OpenApi only)
  • Supports custom classification rules
  • Supports custom match rules for array items and object keys
  • Resolves all $ref pointers, including external and circular
  • Typescript syntax support out of the box
  • No dependencies, can be used in nodejs or browser

Installation

npm install api-smart-diff --save

Usage

Nodejs

import { apiDiff } from 'api-smart-diff'

const diffs = apiDiff(oldSpec, newSpec)
// {
//   action: "add" | "remove" | "replace" | "rename",
//   after: 'value in newSpec',
//   before: 'value in oldSpec',
//   path: ['path, 'in', 'array', 'format'],
//   type: "annotation" | "breaking" | "non-breaking" | "unclassified" | "deprecated"
// }

const merged = apiMerge(oldSpec, newSpec)

Browsers

A browser version of api-smart-diff is also available via CDN:

<script src="https://cdn.jsdelivr.net/npm/api-smart-diff@latest/browser/api-smart-diff.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/api-smart-diff@latest/browser/api-smart-diff.js"></script>

Reference api-smart-diff.min.js in your HTML and use the global variable ApiSmartDiff.

<script>
  var diffs = ApiSmartDiff.apiDiff(oldSpec, newSpec)
  var merged = ApiSmartDiff.apiMerge(oldSpec, newSpec)
</script>

Documentation

Package provides the following public functions:

apiDiff (before, after, options?: CompareOptions): Array<Diff>

Calculates the difference list between two objects and classify difference in accordinance with before document type: OpenApi3, AsyncApi2, JsonSchema.

apiDiffTree (before, after, options?: CompareOptions): object

Calculates the difference tree between two objects and classify difference in accordinance with before document type: OpenApi3, AsyncApi2, JsonSchema.

apiMerge (before, after, options?: MergeOptions): object

Merge two objects and inject difference as meta data.

apiDiff(before, after, options)

The apiDiff function calculates the difference between two objects.

  • before: any - the origin object
  • after: any - the object being compared structurally with the origin object\
  • options: CompareOptions [optional] - comparison options
type CompareOptions = {
  rules?: Rules
  trimStrings?: boolean
  caseSensitive?: boolean
  strictArrays?: boolean
  externalRefs?: { [key: string]: any }
}

Arguments

  • rules - custom match and classification rules
  • trimString - ignore spaces in matching, default false
  • caseSensitive - ignore case in matching, default false
  • strictArrays - use strict match algorithm for array items, default false
  • externalRefs - object with external refs

Result

Function returns array of differences:

type Diff = {
  action: "add" | "remove" | "replace" | "rename"
  path: Array<string | number>
  before?: any
  after?: any
  type: "breaking" | "non-breaking" | "annotation" | "unclassified" | "deprecated"
}

Example

const diffs = apiDiff(before, after)
if (diffs.length) {
  // do something with the changes
}

apiDiffTree(before, after, options)

The apiDiff function calculates the difference between two objects.

  • before: any - the origin object
  • after: any - the object being compared structurally with the origin object\
  • options: CompareOptions [optional] - comparison options

Result

Function returns object with $diff key for all differences:

type Diff = {
  action: "add" | "remove" | "replace" | "rename"
  before?: any
  after?: any
  type: "breaking" | "non-breaking" | "annotation" | "unclassified" | "deprecated"
}

Example

const diff = apiDiffTree(before, after)
// do something with the changes object

apiMerge(before, after, options)

The apiDiff function calculates the difference between two objects.

  • before: any - the origin object
  • after: any - the object being compared structurally with the origin object\
  • options: MergeOptions [optional] - comparison options
type MergeOptions<T> = CompareOptions & {
  resolveUnchangedRefs?: boolean
  arrayMeta?: boolean
  formatMergedMeta?: (diff: T) => any
  metaKey?: string | symbol
}

Arguments

Additional to compare options:

  • arrayMeta - inject meta to arrays for items changes, default false
  • resolveUnchangedRefs - resolve refs even if no changes, default false
  • metaKey - key for diff metadata, default $diff
  • formatMergedMeta - custom formatting function for meta

Result

Function returns merged object with metadata. Metadata includes merged keys and differences:

type MergedMeta = {
  [key: string]: MergedKeyMeta | MergedArrayMeta
}

type MergedKeyMeta = {
  type: DiffType
  action: ActionType
  replaced?: any
}

type MergedArrayMeta = {
  array: { [key: number]: MergedArrayMeta }
}

Example

const apiKey = Symbol("diff")
const merged = apiMerge(before, after, { apiKey })

// do something with merged object

Human-readable change description example

import { apiDiff, changeDoc, changeDocOpenApiRules } from "api-smart-diff"

const diff = apiDiff(before, after, { 
  formatMergedMeta: (diff) => ({ ...diff, description: changeDoc(diff, before, after, changeDocOpenApiRules) })
})

Custom rules

Custom match and classification rules can be defined as object:

type Rules = {
  // root property (or array item) rule
  "/"?: Rule

  // rule for all unspecified properties (or nested array items)
  "/*"?: Rule | Rules | (before) => Rules

  // rule for specified properties
  [key: `/${string}`]?: Rule | Rules | (before) => Rules

  // custom match function for object (or array)
  "#"?: (before, after) => boolean
}

// Change classifier
type Rule = [ 
  DiffType | (ctx: IChangeContext) => DiffType, // add
  DiffType | (ctx: IChangeContext) => DiffType, // remove
  DiffType | (ctx: IChangeContext) => DiffType  // replace (rename)
]

// Current path Change context
interface IChageContext {
  before: any // before value
  after: any // after value
  root: IChageContext // get root Change context
  up: (n?: number) => IChageContext // get parent Change Context
}

Please check predefined rules in /src/rules folder to get examples

Contributing

When contributing, keep in mind that it is an objective of api-smart-diff to have no package dependencies. This may change in the future, but for now, no-dependencies.

Please run the unit tests before submitting your PR: npm test. Hopefully your PR includes additional unit tests to illustrate your change/modification!

License

MIT

About

Compare two Json based API documents (OpenAPI, AsyncAPI, JsonSchema, GraphAPI)

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 97.5%
  • HTML 1.3%
  • JavaScript 1.2%