Reputation: 2141
I want to combine an API specification written using the OpenAPI 3 spec, that is currently divided into multiple files that reference each other using $ref
. How can I do that?
Upvotes: 46
Views: 77830
Reputation: 193
I arrived here because I thought I had to combine openapi specs, because I had a lib
project which I referenced in an app
project, where the openapi spec is created. I noticed that if the relevant class was copied into my app
project, it works, but when referenced from lib
, it only made it into the spec as a generic object
type.
To generate the spec, I'm using the smallrye-open-api-maven-plugin
, but this might be relevant for other plugins as well. It turns out there is a setting <scanPackages>
where I just had to include the lib
project's package path, and that solved my issue.
This does not answer the original question, in the case where you have a legitimate need for multiple specs, but maybe this is helpful for others.
Upvotes: 0
Reputation: 1248
For what it worths redoc manages merge of different openapi files - even in different formats (JSON / YAML).
Upvotes: 5
Reputation: 26
I wrote a Python script which does this (follows $refs
which are filesystem paths and in-lines the contents of those files, recursively):
#!/usr/bin/env python3
# Expand all file-based $ref's inline to produce a single YAML file.
import sys
import os
import yaml
# Is this a local file-based $ref?
# File-based refs look like paths, e.g. "../models/user.yaml".
# Non file-based refs look like URL fragments, e.g. "#/models/user"
def is_file_ref(ref):
return (ref.startswith("/") or ref.startswith("./") or ref.startswith("../")) \
and ref.endswith(".yaml")
# Merge two dictionaries. Throws on key collision.
def merge_or_die(d1, d2):
for k, v in d2.items():
if k in d1:
raise Exception("Refusing to clobber key '%s' with value '%s'" % (k, v))
d1[k] = v
return d1
# Recursively descend through the JSON, expanding any file-based refs inline.
def inline_json(js_in, pwd):
if type(js_in) is dict:
js_out = {}
for k, v in js_in.items():
if k == "$ref" and is_file_ref(v):
if v.startswith("/"):
yml_fpath = v
else:
yml_fpath = os.path.join(pwd, v)
yml_in = open(yml_fpath, "r").read()
v = yaml.safe_load(yml_in)
pwd = os.path.split(yml_fpath)[0]
js_out = merge_or_die(js_out, inline_json(v, pwd))
else:
js_out[k] = inline_json(v, pwd)
return js_out
else:
return js_in
if __name__ == "__main__":
if len(sys.argv) < 2:
sys.stderr.write("Usage: %s <input_file>\n" % sys.argv[0])
sys.exit(1)
fpath_in = sys.argv[1]
if fpath_in.startswith("/"):
yml_fpath = fpath_in
else:
yml_fpath = os.path.join(os.getcwd(), fpath_in)
yml_in = open(yml_fpath, "r").read()
js_in = yaml.safe_load(yml_in)
pwd = os.path.split(yml_fpath)[0]
js_out = inline_json(js_in, pwd)
yml_out = yaml.dump(js_out, sort_keys=False)
print(yml_out)
./inline-yaml.py root.yaml > combined.yaml
Upvotes: 0
Reputation: 9175
I found that the Redocly CLI was a viable option for doing exactly what you need (addressing $ref
's used within specifications).
You can configure the CLI using a configuration file (that must be located in the root of your project directory as well as be written in YAML format) which allows you to specify certain linting rules among other things. But to bundle your specifications, simply perform a bundle
in the directory of where the configuration file is like so:
redocly bundle -o <NameOfFileOrDir>
After running this command, all of your $ref
's will be replaced with the actual code of whatever the $ref
was for, combining them into one definition.
If you have multiple elements defined in your apis
object in your configuration file (i.e. multiple API definitions), it will create a directory named whatever you specified for the -o
option and the folder will contain all of your definitions.
Upvotes: 5
Reputation: 97991
Most OpenAPI tools can work with multi-file OpenAPI definitions and resolve $ref
s dynamically.
If you specifically need to get a single resolved file, Swagger Codegen can do this. Codegen has a CLI version (used in the examples below), a Maven plugin (usage example) and a Docker image.
The input file (-i
argument of the CLI) can be a local file or a URL.
Note: Line breaks are added for readability.
Use Codegen 3.x to resolve OpenAPI 3.0 files:
java -jar swagger-codegen-cli-3.0.35.jar generate
-l openapi-yaml
-i ./path/to/openapi.yaml
-o ./OUT_DIR
-DoutputFile=output.yaml
-l openapi-yaml
outputs YAML, -l openapi
outputs JSON.
-DoutputFile
is optional, the default file name is openapi.yaml
/ openapi.json
.
Use Codegen 2.x to resolve OpenAPI 2.0 files (swagger: '2.0'
):
java -jar swagger-codegen-cli-2.4.28.jar generate
-l swagger-yaml
-i ./path/to/openapi.yaml
-o ./OUT_DIR
-DoutputFile=output.yaml
-l swagger-yaml
outputs YAML, -l swagger
outputs JSON.
-DoutputFile
is optional, the default file name is swagger.yaml
/ swagger.json
.
Upvotes: 27
Reputation: 13497
I wrote a quick tool to do this recently. I call it openapi-merge. There is a library and an associated CLI tool:
In order to use the CLI tool you just write a configuration file and then run npx openapi-merge-cli
. The configuration file is fairly simple and would look something like this:
{
"inputs": [
{
"inputFile": "./gateway.swagger.json"
},
{
"inputFile": "./jira.swagger.json",
"pathModification": {
"stripStart": "/rest",
"prepend": "/jira"
}
},
{
"inputFile": "./confluence.swagger.json",
"disputePrefix": "Confluence",
"pathModification": {
"prepend": "/confluence"
}
}
],
"output": "./output.swagger.json"
}
For more details, see the README on the NPM package.
Upvotes: 35
Reputation: 2141
One way to do this is to use the open-source project speccy.
Open the terminal and install speccy
by running (requires Node.js):
npm install speccy -g
Then run:
speccy resolve path/to/spec.yaml -o spec-output.yaml
Upvotes: 24