Skip to content

About

Remove redundancy from an OpenAPI specification in Go by merging component schemas that describe the same thing, rewriting every $ref that pointed at the copies, and shortening the resulting names.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Go Reference Code Coverage License

A gopher pressing a tall stack of identical boxes down into a single box

One type, one name, one definition.

openapi-compress removes redundancy from an OpenAPI 3.x specification. It finds component schemas that describe the same thing, merges them into one, rewrites every $ref that pointed at the copies, and shortens the resulting names.

Introduction

Specifications that are generated rather than hand-written accumulate duplicates fast. The same object appears as a response body, as an array element, and as a nested property, and each occurrence gets its own component with its own unwieldy name — GetV1PetByPetIDOkJSONResponseMedicalInfo and ListV1PetsOkJSONResponseDataItemsMedicalInfo describing an identical shape.

This module collapses them back down. Two schemas are considered the same when the same JSON validates against both, using openapi-compare — so schemas that differ only in their title or description still merge, while a difference that changes what the schema accepts prevents it.

Optionally it goes further, merging schemas that are merely similar: if two object schemas share most of their properties, they can be widened into a single schema covering both.

Features

  • Deduplicates identical component schemas, keeping one canonical definition
  • Merges similar schemas above a configurable similarity threshold
  • Rewrites every $ref throughout the document, including deep inside nested schemas
  • Deduplicates parameters in the same way
  • Shortens the names of merged schemas, dropping generated noise like OkJSONResponse

How similarity works

At the default threshold of 1.0 only equivalent schemas merge. Below that, object schemas are scored by a weighted Jaccard index over their property names:

score = Σ weight(p) / |union of property names|

where a property present in both with the same shape scores 1.0, and one present in both with a different shape scores 0.5. The threshold steps down gradually, running each level until no further merges are found, so the most confident merges always happen first.

A bare scalar is never merged: a string, number, integer or boolean with nothing but a type and documentation, such as a component idRequest that is only {"type": "string"}. Its name and description are all it carries, and merging by shape would erase exactly those, giving IDs, emoji names and time zones one type. A scalar with a constraint, such as a format or an enum, merges like any other schema.

What a merge keeps

Of the schemas that merge, the one kept is one the specification named, else the one with the most references, else the one with the shortest name, else the alphabetically first: the {id} object a specification defines keeps its own name rather than taking one derived from a single place that also used it. A schema openapi-flatten moved out of the document and named itself carries x-flattened-from, given its MarkOrigin; compress reads and removes it before comparing anything, so it never keeps two schemas apart.

Examples are kept from every schema that merges, down to each property, item and alternative: where the one kept has none, it takes one from those merged into it.

A description says what a schema is used for in one place, not what shape it has. When the schemas that merge disagree on it, each one's description moves beside the $refs that pointed at it, and the schema kept has none, so no reference shows another's. A reference with a description of its own keeps it.

Usage

go get -tool github.com/MarkRosemaker/openapi-compress/cmd/openapi-compress

or

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

doc, err := openapi.LoadFromFile("api/openapi.json")
if err != nil {
    log.Fatal(err)
}

// Exact-shape deduplication only.
if err := compress.Document(doc, compress.Config{}); err != nil {
    log.Fatal(err)
}

To also merge schemas that merely overlap, lower the threshold:

err := compress.Document(doc, compress.Config{
    MinSimilarity:  0.8,  // merge schemas sharing ≥80% of their properties
    SimilarityStep: 0.05, // step down by this much per round
})
Field Default Purpose
MinSimilarity 1.0 Lowest similarity at which two schemas may merge. 1.0 means equivalent shapes only.
SimilarityStep 0.05 How far the threshold drops between rounds.
SkipNameShortening false Keep the original names instead of shortening merged ones.

Renaming a schema on its own — without compressing anything — is openapi-edit's job, and this module uses it internally to shorten the names of merged schemas:

err := edit.RenameSchema(doc, "OldName", "NewName")

Command line

openapi-compress -spec api/openapi.json -minsim 0.8
Flag Default Purpose
-spec api/openapi.json Path to the specification, rewritten in place
-minsim 1 Minimum similarity for a merge
-simstep 0.05 Threshold reduction between rounds

If the specification was valid on the way in, the result is validated before it is written back.

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 (this module) Deduplicate and merge equivalent component schemas
openapi-merge 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

This module pairs naturally with openapi-flatten: flattening names every inline type, which necessarily creates duplicates, and compressing collapses them again. Run flatten first.

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.

About

Remove redundancy from an OpenAPI specification in Go by merging component schemas that describe the same thing, rewriting every $ref that pointed at the copies, and shortening the resulting names.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages