https://github.com/udamir/api-smart-diff
Compare two Json based API documents (OpenAPI, AsyncAPI, JsonSchema, GraphAPI)
https://github.com/udamir/api-smart-diff
Last synced: 5 months ago
JSON representation
Compare two Json based API documents (OpenAPI, AsyncAPI, JsonSchema, GraphAPI)
- Host: GitHub
- URL: https://github.com/udamir/api-smart-diff
- Owner: udamir
- License: mit
- Created: 2022-03-26T17:42:23.000Z (almost 4 years ago)
- Default Branch: master
- Last Pushed: 2024-07-08T23:01:33.000Z (over 1 year ago)
- Last Synced: 2024-12-08T14:52:12.555Z (about 1 year ago)
- Language: TypeScript
- Homepage:
- Size: 946 KB
- Stars: 20
- Watchers: 1
- Forks: 5
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# api-smart-diff

This package provides utils to compute the diff between two Json based API documents - [online demo](https://udamir.github.io/api-smart-diff/)
## Purpose
- Generate API changelog
- Identify breaking changes
- Ensure API versioning consistency
## Supported API specifications
- [OpenApi 3.0](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md)
- [AsyncApi 2.x](https://v2.asyncapi.com/docs/reference)
- [JsonSchema](https://json-schema.org/draft/2020-12/json-schema-core.html)
- GraphQL via [GraphApi](https://github.com/udamir/graphapi)
- ~~[Swagger 2.0](https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md)~~
- ~~[AsyncApi 3.x](https://www.asyncapi.com/docs/specifications/)~~ (Roadmap)
- ~~gRPC~~ (Roadmap)
## Features
- Generate diff for supported specifications
- Generate merged document with changes in metadata
- Classify all changes as breaking, non-breaking, deprecated and annotation
- Human-readable change description
- Supports custom classification rules
- Supports custom comparison or match rules
- Supports custom transformations
- Supports custom human-readable changes annotation
- Resolves all $ref pointers, including circular
- Typescript syntax support out of the box
- Can be used in nodejs or browser
## External $ref
If schema contains an external $ref, you should bundle it via [api-ref-bundler](https://github.com/udamir/api-ref-bundler) first.
## Installation
```SH
npm install api-smart-diff --save
```
or
```SH
yarn add api-smart-diff
```
## Usage
### Nodejs
```ts
import { apiCompare } from 'api-smart-diff'
const { diffs, merged } = apiCompare(before, after)
// diff:
// {
// action: "add" | "remove" | "replace" | "rename",
// after: 'value in after',
// before: 'value in before',
// description: 'human-readable description'
// path: ['path, 'in', 'array', 'format'],
// type: "annotation" | "breaking" | "non-breaking" | "unclassified" | "deprecated"
// }
// merged meta:
// {
// action: "add" | "remove" | "replace" | "rename",
// type: "annotation" | "breaking" | "non-breaking" | "unclassified" | "deprecated",
// replaced: "value in before",
// }
```
### Browsers
A browser version of `api-smart-diff` is also available via CDN:
```html
```
Reference `api-smart-diff.min.js` in your HTML and use the global variable `ApiSmartDiff`.
```HTML
var { diffs, merged } = ApiSmartDiff.apiCompare(before, after)
```
## Documentation
Package provides the following public functions:
`apiCompare (before, after, options?: CompareOptions): { diffs: Diff[], merged: object }`
> Calculates the difference and merge two objects and classify difference in accordinance with before document type
### **apiCompare(before, after, options)**
The apiDiff function calculates the difference between two objects.
#### *Arguments*
- `before: any` - the origin object
- `after: any` - the object being compared structurally with the origin object\
- `options: CompareOptions` [optional] - comparison options
```ts
export type ComapreOptions = {
rules?: CompareRules // custom rules for compare
metaKey?: string | symbol // metakey for merge changes
arrayMeta?: boolean // add changes to arrays via metakey
annotateHook?: AnnotateHook // custom format hook
externalSources?: { // resolved external $ref sources
before?: Record
after?: Record
}
}
```
#### *Result*
Function returns object with `diffs` array and `merged` object with metadata
```ts
type Diff = {
action: "add" | "remove" | "replace" | "rename"
path: Array
description?: string
before?: any
after?: any
type: "breaking" | "non-breaking" | "annotation" | "unclassified" | "deprecated"
}
type MergeMeta = DiffMeta | MergeArrayMeta
type MergeArrayMeta = { array: Record }
export type DiffMeta = {
action: "add" | "remove" | "replace" | "rename"
type: "breaking" | "non-breaking" | "annotation" | "unclassified" | "deprecated"
replaced?: any
}
```
#### *Example*
```ts
const metaKey = Symbol("diff")
const { diffs, merged } = apiCompare(before, after, { metaKey })
// do something with diffs or merged object
```
### **Custom rules**
Custom compare rules can be defined as CrawlRules:
```ts
import { CrawlRules } from "json-crawl"
type CompareRules = CrawlRules
type CompareRule = {
$?: ClassifyRule // classifier for current node
compare?: CompareResolver // compare handler for current node
transform?: CompareTransformResolver[] // transformations before compare/merge
mapping?: MappingResolver // keys mapping rules
annotate?: ChangeAnnotationResolver // resolver for annotation template
}
// Change classifier
type ClassifyRule = [
DiffType | (ctx: ComapreContext) => DiffType, // add
DiffType | (ctx: ComapreContext) => DiffType, // remove
DiffType | (ctx: ComapreContext) => DiffType, // replace (rename)
DiffType | (ctx: ComapreContext) => DiffType, // (optional) reversed rule for add
DiffType | (ctx: ComapreContext) => DiffType, // (optional) reversed rule for remove
DiffType | (ctx: ComapreContext) => DiffType // (optional) reversed rule for replace (rename)
]
// Compare context
type ComapreContext = {
before: NodeContext // before node context
after: NodeContext // after node context
rules: CompareRules // rules for compared nodes
options: ComapreOptions // compare options
}
// Node context
type NodeContext = {
path: JsonPath
key: string | number
value: unknown
parent?: unknown
root: unknown
}
// Custom compare resolver
type CompareResolver = (ctx: ComapreContext) => CompareResult | void
// Transformation rules
type CompareTransformResolver = (before: T, after: T) => [T, T]
// Mapping rules
type MappingResolver = (
before: Record | unknown[],
after: Record | unknown[],
ctx: ComapreContext
) => MapKeysResult
type MapKeysResult = {
added: Array
removed: Array
mapped: Record
}
// Annotation tempalte resolver
type ChangeAnnotationResolver = (diff: Diff, ctx: ComapreContext) => AnnotateTemplate | undefined
type AnnotateTemplate = {
template: string,
params?: { [key: string]: AnnotateTemplate | string | number | undefined }
}
```
## Contributing
When contributing, keep in mind that it is an objective of `api-smart-diff` to have no additional package dependencies. This may change in the future, but for now, no new dependencies.
Please run the unit tests before submitting your PR: `yarn test`. Hopefully your PR includes additional unit tests to illustrate your change/modification!
## License
MIT