openapi pre, pre apibp conversion

This commit is contained in:
Markus Kahl
2021-06-28 13:02:03 +01:00
parent 2ac1497c61
commit dc8615de7e
8 changed files with 196 additions and 9 deletions
+1 -1
View File
@@ -260,7 +260,7 @@ Updates the given group by applying the attributes provided in the body.
}
}
## Delete group [/api/v3/group/{id}]
## Delete group [/api/v3/groups/{id}]
## Delete group [DELETE]
@@ -135,6 +135,7 @@ import {
slideToggleSelector
} from "core-app/shared/components/slide-toggle/slide-toggle.component";
import { BackupComponent, backupSelector } from "core-app/core/setup/globals/components/admin/backup.component";
import { DocsComponent, docsSelector } from "core-app/core/setup/globals/components/docs/docs.component";
import {
EnterpriseBaseComponent,
enterpriseBaseSelector,
@@ -220,7 +221,8 @@ export const globalDynamicComponents:OptionalBootstrapDefinition[] = [
{ selector: editableQueryPropsSelector, cls: EditableQueryPropsComponent },
{ selector: slideToggleSelector, cls: SlideToggleComponent },
{ selector: backupSelector, cls: BackupComponent },
{ selector: opInAppNotificationBellSelector, cls: InAppNotificationBellComponent }
{ selector: opInAppNotificationBellSelector, cls: InAppNotificationBellComponent },
{ selector: docsSelector, cls: DocsComponent }
];
+58 -2
View File
@@ -1,7 +1,63 @@
module API
module OpenAPI
def self.spec(version: :stable)
API::OpenAPI::BlueprintImport.convert version: version
extend self
def spec(version: :stable)
spec_path = Rails.application.root.join("docs/api/apiv3/openapi-spec.yml")
if spec_path.exist?
assemble_spec spec_path
else
API::OpenAPI::BlueprintImport.convert version: version, single_file: true
end
end
def assemble_spec(file_path)
spec = YAML.load File.read(file_path.to_s)
substitute_refs(spec, path: file_path.parent, root_path: file_path.parent)
end
def substitute_refs(spec, path:, root_path:, root_spec: spec)
if spec.is_a?(Hash)
if spec.size == 1 && spec.keys.first == "$ref"
ref_path = path.join spec.values.first
ref_value = YAML.load File.read(ref_path.to_s)
resolve_refs ref_value, path: ref_path.parent, root_path: root_path, root_spec: root_spec
else
spec.map { |k, v| [k, substitute_refs(v, path: path, root_path: root_path, root_spec: root_spec)] }.to_h
end
elsif spec.is_a?(Array)
spec.map { |s| substitute_refs s, path: path, root_path: root_path, root_spec: root_spec }
else
spec
end
end
def resolve_refs(spec, path:, root_path:, root_spec:)
if spec.is_a?(Hash)
if spec.size == 1 && spec.keys.first == "$ref"
ref_path = spec.values.first
if ref_path.start_with?(".")
schema_file = path.join(ref_path).to_s.sub(root_path.to_s, ".")
schema_path = path.join(ref_path).parent.to_s.sub(root_path.to_s, "").split("/").drop(1)
schema_name = root_spec.dig(*schema_path).find { |k, v| v["$ref"] == schema_file }.first
schema_ref = path.join(ref_path).parent.join(schema_name).to_s.sub(root_path.to_s, "#")
{ spec.keys.first => schema_ref }
else
spec
end
else
spec.map { |k, v| [k, resolve_refs(v, path: path, root_path: root_path, root_spec: root_spec)] }.to_h
end
elsif spec.is_a?(Array)
spec.map { |v| resolve_refs(v, path: path, root_path: root_path, root_spec: root_spec) }
else
spec
end
end
end
end
+122 -3
View File
@@ -21,7 +21,7 @@ module API
@include_directive_regex ||= /\<\!\-\-\s*include\((.*)\)\s*\-\-\>/
end
def convert(version: :stable)
def convert(version: :stable, single_file: false)
input_file = Rails.application.root.join("docs/api/apiv3-doc-#{version}.apib")
md_file = Tempfile.new("apibp.md").path
assemble_file input_path: input_file, output_path: md_file
@@ -31,10 +31,119 @@ module API
add_security! spec
amend_schemas! spec, apibp: File.read(md_file)
if !single_file
split_up_schemas! spec
split_up_paths! spec
split_up_tags! spec
end
spec
ensure
FileUtils.rm_f md_file if File.exist? md_file
end
def split_up_schemas!(spec)
file_path = Rails.application.root.join "docs/api/apiv3/components/schemas"
FileUtils.mkdir_p file_path.to_s
new_schemas = spec["components"]["schemas"].map do |name, content|
identifier = name.underscore
file_name = "#{identifier}.yml"
File.open(file_path.join(file_name), "w") do |f|
f.write "# Schema: #{name}\n"
f.write content.to_yaml
end
[name, { "$ref" => "./components/schemas/#{file_name}"}]
end
spec["components"]["schemas"] = new_schemas.to_h
end
def split_up_tags!(spec)
file_path = Rails.application.root.join "docs/api/apiv3/tags"
FileUtils.mkdir_p file_path.to_s
new_tags = spec["tags"].map do |value|
identifier = value["name"].downcase.gsub("&", "and").gsub(" ", "_")
file_name = "#{identifier}.yml"
File.open(file_path.join(file_name), "w") do |f|
f.write value.to_yaml
end
{ "$ref" => "./tags/#{file_name}"}
end
spec["tags"] = new_tags
end
def split_up_paths!(spec)
file_path = Rails.application.root.join "docs/api/apiv3/paths"
FileUtils.mkdir_p file_path.to_s
new_paths = spec["paths"].map do |path, content|
segments = path.sub("/api/v3", "").split("/").reject(&:blank?)
(0..(segments.size - 1)).each do |i|
if i > 0 && segments[i].end_with?("id}")
before = segments[i - 1]
after = before.singularize
# certain words like 'news' can't be singularized
if before == after
segments[i - 1] = "#{before}_item"
else
segments[i - 1] = after
end
end
end
identifier = segments.reject { |s| s.end_with?("id}") }.join("_").presence || "root"
file_name = "#{identifier}.yml"
File.open(file_path.join(file_name), "w") do |f|
f.write "# #{path}\n"
f.write fix_operation_ids!(fix_references!(content.dup, context: spec)).to_yaml
end
[path, { "$ref" => "./paths/#{file_name}"}]
end
raise "Splitting up into paths failed! Expected same number of paths. " unless new_paths.size == spec["paths"].size
spec["paths"] = new_paths.to_h
end
def fix_operation_ids!(spec)
spec.each do |key, value|
if value.is_a? Hash
fix_operation_ids! value
elsif key == "operationId"
spec[key] = spec[key].gsub " ", "_"
end
end
spec
end
def fix_references!(spec, context:)
spec.each do |key, value|
if value.is_a? Hash
fix_references! value, context: context
elsif value.is_a? Array
spec[key] = value.map { |v| v.is_a?(Hash) ? fix_references!(v.dup, context: context) : v }
elsif key == "$ref" && value.start_with?("#/components")
spec[key] = '.' + context.dig(*(value.split("/").drop(1) + ['$ref']))
end
end
spec
end
def add_security!(spec)
spec["components"]["securitySchemes"] = {
@@ -106,7 +215,6 @@ module API
"href" => {
"type" => "string",
"nullable" => true,
"format" => "uri",
"description" => "URL to the referenced resource (might be relative)"
},
"title" => {
@@ -273,7 +381,7 @@ module API
link = {}
value = {
"allOf" => [{ "$ref" => "#/components/schemas/Link" }, link]
"allOf" => [{ "$ref" => "./link.yml" }, link]
}
set_description! link, row, desc_index
@@ -351,6 +459,17 @@ module API
add_conditions! value, row, cond_index
if type == "Formattable"
value.delete "type"
value = {
"allOf" => [
{ "$ref" => "./formattable.yml" },
value
]
}
end
[name, value]
end
+12 -2
View File
@@ -49,9 +49,19 @@ namespace :api do
desc 'Saves the API spec (OAS3.0) to ./docs/api/openproject-apiv3-<branch>.yml'
task :update_spec, [:branch] => [:environment] do |task, args|
branch = (args[:branch] || "stable").to_sym
spec = API::OpenAPI::BlueprintImport.convert version: branch
spec = API::OpenAPI::BlueprintImport.convert version: branch, single_file: false
File.open(Rails.application.root.join("docs/api/apiv3-oas-#{branch}.yml"), "w") do |f|
File.open(Rails.application.root.join("docs/api/apiv3/openapi-spec.yml"), "w") do |f|
f.write spec.to_yaml
end
end
desc 'Saves the API spec (OAS3.0) to ./docs/api/openproject-apiv3-single.yml'
task :assemble_spec, [:branch] => [:environment] do |task, args|
branch = (args[:branch] || "stable").to_sym
spec = API::OpenAPI.spec
File.open(Rails.application.root.join("docs/api/apiv3/openapi-spec-single.yml"), "w") do |f|
f.write spec.to_yaml
end
end