MarkRosemaker/openapi-merge

Merge two OpenAPI objects into one that covers both, reconciling schemas inferred from independent samples of real data. Handles partial evidence such as values observed only as null, integer and number widening, and dates encoded either as strings or as timestamps.

★ 0Forks 0GoGitHub ↗Compare
apiapi-toolsgojsonjson-schemamergeopenapischemaschema-inferenceswagger

README

Go Reference Code Coverage License

A gopher clicking two incomplete jigsaw pieces together into one complete piece

Two views of the same endpoint, reconciled.

Code Coverage

openapi-merge combines two OpenAPI 3.x objects into one that covers both. It exists for the problem of incomplete evidence: when a schema is inferred from a sample of real data, each sample tells you only part of the story, and the parts have to be reconciled.

Introduction

Observe GET /users/{id} once and you might see {"id": 1, "name": "Alice"}. Observe it again and you get {"id": 2, "name": "Bob", "nickname": null}. Neither response is the schema. The schema is what you get by merging them: three properties, one of them optional, one of them only ever seen as null.

That is what this module does. It is used by openapi-enrich, which builds specifications from recorded HTTP traffic and calls in here every time a second observation of the same endpoint arrives.

Merging is destructive and asymmetric by design: b is merged into a, in place. If the two cannot be reconciled, an error is returned describing the exact JSON path at which they conflict.

Features

Beyond combining properties and widening optionality, the merge handles the particular ways that sample-derived schemas disagree:

  • Null — a value observed only as null has the type null. Merged with a real type, the result is that type, made nullable (["string", "null"]), rather than a conflict.
  • Arrays only ever seen empty — {"type": "array", "maxItems": 0} says nothing about the items, so the other side's items are adopted, and item bounds widen to cover both sides.
  • Numeric widening — an integer in one sample and a floating-point number in another merge to a number.
  • Dates in two encodings — a value seen as a date-time string in one sample and as a Unix timestamp integer in another becomes a oneOf of the two, rather than one silently discarding the other.
  • oneOf routing — when one side already covers several shapes, the other is merged into whichever branch it matches.
  • Scalar-or-array parameters — a parameter that appeared as a bare value in one sample and as an array in another merges the value into the array's item schema.
  • Enums — an example value not yet present in an enum is added to it.

Usage

go get github.com/MarkRosemaker/openapi-merge
import (
    "github.com/MarkRosemaker/openapi"
    merge "github.com/MarkRosemaker/openapi-merge"
)

// b is merged into a; a is modified in place.
if err := merge.Schema(a, b, false); err != nil {
    log.Fatal(err) // e.g. properties["age"].type: "string" != "integer"
}

The final argument to Schema marks whether the schemas describe a parameter, which enables the scalar-or-array reconciliation above — that mismatch is an artifact of how query parameters get sampled, and applying it to a request body would mask a genuine conflict.

Merging is available for each object kind:

Function Merges
merge.Schema(a, b *openapi.Schema, isParam bool) Two schemas
merge.SchemaRefs(a *openapi.SchemaRefs, b openapi.SchemaRefs) Two sets of named schemas
merge.Parameter(a, b *openapi.Parameter) Two parameters
merge.Response(a, b *openapi.Response) Two responses
merge.MediaType(a, b *openapi.MediaType) Two media types
merge.Content(a *openapi.Content, b openapi.Content) Two content maps

Errors carry the full JSON path to the conflict, via errpath.

The openapi family

Module Purpose
openapi Parse, validate, and write OpenAPI 3.x specifications
openapi-compare Compare specification objects — exact equality and shape equivalence
openapi-edit Safe structural edits, such as renaming a schema and rewriting every $ref to it
openapi-flatten Promote inline definitions into named components entries
openapi-compress Deduplicate and merge equivalent component schemas
openapi-merge (this module) Merge schemas that were inferred independently from different samples
openapi-enrich Infer specification content from observed HTTP traffic
openapi-codegen Generate Go types, clients, and servers from a specification

Two neighbours are easy to confuse with this one:

  • openapi-compare reports on two objects without changing them. This module changes them.
  • openapi-compress merges schemas too, but for a different reason — it collapses redundancy within a single finished document, whereas this module reconciles partial evidence about the same thing.

Additional Information

Contributing

Contributions are welcome — please open an issue or a pull request on GitHub.

License

This project is licensed under the Apache 2.0 License.

Contributors

claudeMarkRosemaker

Issues