2021-06-23 16:44:20 +01:00
|
|
|
module API
|
|
|
|
|
module OpenAPI
|
2021-06-28 13:02:03 +01:00
|
|
|
extend self
|
|
|
|
|
|
2021-06-29 08:54:05 +01:00
|
|
|
def spec
|
2021-11-29 12:15:23 +01:00
|
|
|
spec = if Rails.env.development?
|
|
|
|
|
load_spec
|
|
|
|
|
else
|
|
|
|
|
memoized_spec
|
|
|
|
|
end
|
2021-06-29 08:54:05 +01:00
|
|
|
|
2021-11-29 12:15:23 +01:00
|
|
|
spec["servers"] = [
|
2021-06-29 08:54:05 +01:00
|
|
|
{
|
|
|
|
|
"description" => "This server",
|
|
|
|
|
"url" => "#{Setting.protocol}://#{Setting.host_name}/"
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
|
2021-11-29 12:15:23 +01:00
|
|
|
spec
|
|
|
|
|
end
|
|
|
|
|
|
2022-01-07 17:44:21 +01:00
|
|
|
def assemble_spec(file_path)
|
2024-01-04 17:01:17 +01:00
|
|
|
spec = YAML.safe_load_file(file_path.to_s)
|
2022-01-07 17:44:21 +01:00
|
|
|
|
|
|
|
|
substitute_refs(spec, path: file_path.parent, root_path: file_path.parent)
|
|
|
|
|
rescue Psych::SyntaxError => e
|
|
|
|
|
raise "Failed to load #{file_path}: #{e.class} #{e.message}"
|
|
|
|
|
end
|
|
|
|
|
|
2021-11-29 12:15:23 +01:00
|
|
|
private
|
|
|
|
|
|
|
|
|
|
def memoized_spec
|
|
|
|
|
@memoized_spec ||= load_spec
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def load_spec
|
|
|
|
|
spec_path = Rails.application.root.join("docs/api/apiv3/openapi-spec.yml")
|
|
|
|
|
|
|
|
|
|
if spec_path.exist?
|
|
|
|
|
assemble_spec spec_path
|
|
|
|
|
else
|
|
|
|
|
raise "Could not find openapi-spec.yml under #{spec_path}"
|
|
|
|
|
end
|
2021-06-28 13:02:03 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def substitute_refs(spec, path:, root_path:, root_spec: spec)
|
2021-06-30 09:57:00 +01:00
|
|
|
case spec
|
|
|
|
|
when Hash
|
2021-06-29 14:42:54 +01:00
|
|
|
substitute_refs_in_hash spec, path:, root_path:, root_spec:
|
2021-06-30 09:57:00 +01:00
|
|
|
when Array
|
2021-06-28 13:02:03 +01:00
|
|
|
spec.map { |s| substitute_refs s, path:, root_path:, root_spec: }
|
|
|
|
|
else
|
|
|
|
|
spec
|
|
|
|
|
end
|
|
|
|
|
end
|
2021-06-29 15:06:57 +01:00
|
|
|
|
2021-06-29 14:42:54 +01:00
|
|
|
def substitute_refs_in_hash(spec, path:, root_path:, root_spec: spec)
|
|
|
|
|
if spec.size == 1 && spec.keys.first == "$ref"
|
|
|
|
|
ref_path = path.join spec.values.first
|
2024-01-04 17:01:17 +01:00
|
|
|
ref_value = YAML.safe_load_file(ref_path.to_s)
|
2021-06-29 14:42:54 +01:00
|
|
|
|
|
|
|
|
resolve_refs ref_value, path: ref_path.parent, root_path:, root_spec:
|
|
|
|
|
else
|
|
|
|
|
spec.transform_values { |v| substitute_refs(v, path:, root_path:, root_spec:) }
|
|
|
|
|
end
|
2021-07-20 16:29:10 +02:00
|
|
|
rescue Psych::SyntaxError => e
|
|
|
|
|
raise "Failed to load #{ref_path}: #{e.class} #{e.message}"
|
2021-06-29 14:42:54 +01:00
|
|
|
end
|
2021-06-28 13:02:03 +01:00
|
|
|
|
|
|
|
|
def resolve_refs(spec, path:, root_path:, root_spec:)
|
2021-06-30 09:57:00 +01:00
|
|
|
case spec
|
|
|
|
|
when Hash
|
2021-06-29 14:42:54 +01:00
|
|
|
resolve_refs_in_hash spec, path:, root_path:, root_spec:
|
2021-06-30 09:57:00 +01:00
|
|
|
when Array
|
2021-06-29 14:42:54 +01:00
|
|
|
spec.map { |v| resolve_refs v, path:, root_path:, root_spec: }
|
|
|
|
|
else
|
|
|
|
|
spec
|
|
|
|
|
end
|
|
|
|
|
end
|
2021-06-28 13:02:03 +01:00
|
|
|
|
2021-06-29 14:42:54 +01:00
|
|
|
def resolve_refs_in_hash(spec, path:, root_path:, root_spec:)
|
|
|
|
|
if spec.size == 1 && spec.keys.first == "$ref"
|
2023-03-07 15:07:44 +01:00
|
|
|
resolve_ref(spec, path:, root_path:, root_spec:)
|
2021-06-29 15:06:57 +01:00
|
|
|
else
|
|
|
|
|
spec.transform_values { |v| resolve_refs v, path:, root_path:, root_spec: }
|
|
|
|
|
end
|
|
|
|
|
end
|
2021-06-28 13:02:03 +01:00
|
|
|
|
2021-06-29 15:06:57 +01:00
|
|
|
def resolve_ref(spec, path:, root_path:, root_spec:)
|
|
|
|
|
ref_path = spec.values.first
|
2021-06-29 14:42:54 +01:00
|
|
|
|
2021-06-29 15:06:57 +01:00
|
|
|
if ref_path.start_with?(".")
|
2021-06-30 09:57:00 +01:00
|
|
|
{ spec.keys.first => schema_ref(ref_path, path:, root_path:, root_spec:) }
|
2021-06-28 13:02:03 +01:00
|
|
|
else
|
2021-06-29 15:06:57 +01:00
|
|
|
spec
|
2021-06-28 13:02:03 +01:00
|
|
|
end
|
2021-06-23 16:44:20 +01:00
|
|
|
end
|
2021-06-30 09:57:00 +01:00
|
|
|
|
|
|
|
|
def schema_ref(ref_path, path:, root_path:, root_spec:)
|
2023-03-07 15:07:44 +01:00
|
|
|
name = schema_name(ref_path, path:, root_path:, root_spec:)
|
2021-06-30 09:57:00 +01:00
|
|
|
|
|
|
|
|
path.join(ref_path).parent.join(name).to_s.sub(root_path.to_s, "#")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def schema_file(ref_path, path:, root_path:)
|
|
|
|
|
path.join(ref_path).to_s.sub root_path.to_s, "."
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def schema_path(ref_path, path:, root_path:)
|
|
|
|
|
path.join(ref_path).parent.to_s.sub(root_path.to_s, "").split("/").drop 1
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def schema_name(ref_path, path:, root_path:, root_spec:)
|
2023-03-07 15:07:44 +01:00
|
|
|
file = schema_file(ref_path, path:, root_path:)
|
|
|
|
|
spec_path = schema_path(ref_path, path:, root_path:)
|
2021-06-30 09:57:00 +01:00
|
|
|
|
2021-11-29 12:15:23 +01:00
|
|
|
spec_files = root_spec.dig(*spec_path)
|
|
|
|
|
|
|
|
|
|
raise "Path not defined #{spec_path}" unless spec_files
|
|
|
|
|
|
|
|
|
|
spec_file = spec_files.find { |_k, v| v["$ref"] == file }&.first
|
2023-12-12 09:35:26 +00:00
|
|
|
raise "'#{file}' not defined under #{spec_path.join('.')} in `docs/api/apiv3/openapi-spec.yml`" unless spec_file
|
2021-11-29 12:15:23 +01:00
|
|
|
|
|
|
|
|
spec_file
|
2021-06-30 09:57:00 +01:00
|
|
|
end
|
2021-06-23 16:44:20 +01:00
|
|
|
end
|
|
|
|
|
end
|