lutaml-openapi 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/frontend/dist/app.iife.js +23 -0
- data/frontend/dist/style.css +1 -0
- data/frontend/index.html +12 -0
- data/frontend/package-lock.json +3651 -0
- data/frontend/package.json +27 -0
- data/frontend/src/App.vue +60 -0
- data/frontend/src/app.ts +13 -0
- data/frontend/src/components/OverviewView.vue +28 -0
- data/frontend/src/components/PathsView.vue +46 -0
- data/frontend/src/components/SchemasView.vue +54 -0
- data/frontend/src/style.css +410 -0
- data/frontend/tsconfig.json +20 -0
- data/frontend/vite.config.ts +28 -0
- data/lib/lutaml/openapi/corpus_audit.rb +84 -0
- data/lib/lutaml/openapi/spa.rb +60 -0
- data/lib/lutaml/openapi/version.rb +1 -1
- data/lib/lutaml/openapi.rb +2 -0
- data/rakelib/corpus.rake +39 -0
- metadata +22 -3
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/openapi"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module Openapi
|
|
7
|
+
# The metanorma-ogc#153 corpus audit as a repeatable task: parse
|
|
8
|
+
# every vendored corpus fixture, assert the lossless round-trip,
|
|
9
|
+
# report the description/summary coverage matrix.
|
|
10
|
+
module CorpusAudit
|
|
11
|
+
FIXTURES = File.expand_path("../../../spec/fixtures",
|
|
12
|
+
__dir__)
|
|
13
|
+
|
|
14
|
+
def self.files
|
|
15
|
+
Dir.glob("#{FIXTURES}/*.{yaml,yml,json}")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.matrix
|
|
19
|
+
files.map { |path| row(path) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.row(path)
|
|
23
|
+
doc = Lutaml::Openapi.parse(File.read(path))
|
|
24
|
+
raw = doc.to_hash
|
|
25
|
+
schema_stats = schema_stats(raw)
|
|
26
|
+
{ file: File.basename(path), openapi: raw["openapi"],
|
|
27
|
+
lossless: lossless?(path, doc), **schema_stats }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.schema_stats(raw)
|
|
31
|
+
schemas = raw.dig("components", "schemas") || {}
|
|
32
|
+
documented = schemas.values.count do |s|
|
|
33
|
+
s.is_a?(Hash) && s["description"].is_a?(String) &&
|
|
34
|
+
!s["description"].strip.empty?
|
|
35
|
+
end
|
|
36
|
+
{ schemas: schemas.size, schemas_documented: documented,
|
|
37
|
+
**operation_stats(raw) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.operation_stats(raw)
|
|
41
|
+
operations = 0
|
|
42
|
+
with_text = 0
|
|
43
|
+
each_operation(raw) do |op|
|
|
44
|
+
operations += 1
|
|
45
|
+
text = op["summary"] || op["description"]
|
|
46
|
+
with_text += 1 if text.is_a?(String) && !text.strip.empty?
|
|
47
|
+
end
|
|
48
|
+
{ operations: operations, operations_with_text: with_text }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.each_operation(raw, &block)
|
|
52
|
+
paths = raw["paths"] || {}
|
|
53
|
+
paths.each_value do |item|
|
|
54
|
+
next unless item.is_a?(Hash)
|
|
55
|
+
|
|
56
|
+
yield_operations(item, &block)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.yield_operations(item, &)
|
|
61
|
+
item.each_value do |operation_entry|
|
|
62
|
+
next unless operation?(operation_entry)
|
|
63
|
+
|
|
64
|
+
yield operation_entry
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.operation?(operation_entry)
|
|
69
|
+
operation_entry.is_a?(Hash) && operation_entry["responses"]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.lossless?(path, doc)
|
|
73
|
+
raw = Lutaml::Openapi.parse_raw(File.read(path))
|
|
74
|
+
# The model is JSON-keyed: YAML integer keys (unquoted response
|
|
75
|
+
# codes) stringify through the pipeline, so compare against the
|
|
76
|
+
# JSON-normalized raw. to_hash is the serialization contract:
|
|
77
|
+
# 3.0 sources denormalize back on it. Hash == is deep and
|
|
78
|
+
# key-order-insensitive.
|
|
79
|
+
normalized = JSON.parse(JSON.generate(raw))
|
|
80
|
+
doc.to_hash == normalized
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module Openapi
|
|
7
|
+
# Generates a single-file SPA documentation site for an OpenAPI
|
|
8
|
+
# document. The frontend bundle (frontend/dist) is built by the
|
|
9
|
+
# frontend toolchain and is expected to exist at generation time —
|
|
10
|
+
# it enters the gem only immediately before a release.
|
|
11
|
+
module Spa
|
|
12
|
+
DIST = File.expand_path("../../../frontend/dist", __dir__)
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def generate(document, title: nil)
|
|
17
|
+
app_js = File.read(File.join(DIST, "app.iife.js"))
|
|
18
|
+
style = File.join(DIST, "style.css")
|
|
19
|
+
data = safe_json(document.to_hash)
|
|
20
|
+
|
|
21
|
+
title ||= document.to_hash.dig("info", "title") || "OpenAPI document"
|
|
22
|
+
|
|
23
|
+
build_html(escape(title), data, app_js, style_content(style))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# JSON embedded in a <script> element: escape "<" so payload
|
|
27
|
+
# strings containing "</script>" (or any markup) cannot break out
|
|
28
|
+
def safe_json(obj)
|
|
29
|
+
JSON.generate(obj).gsub("<", "\\u003c")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def build_html(title, data, app_script, css)
|
|
33
|
+
<<~HTML
|
|
34
|
+
<!DOCTYPE html>
|
|
35
|
+
<html lang="en">
|
|
36
|
+
<head>
|
|
37
|
+
<meta charset="UTF-8">
|
|
38
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
39
|
+
<title>#{title}</title>
|
|
40
|
+
<style>#{css}</style>
|
|
41
|
+
</head>
|
|
42
|
+
<body>
|
|
43
|
+
<div id="app"></div>
|
|
44
|
+
<script>window.OPENAPI_DATA = #{data};</script>
|
|
45
|
+
<script>#{app_script}</script>
|
|
46
|
+
</body>
|
|
47
|
+
</html>
|
|
48
|
+
HTML
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def style_content(path)
|
|
52
|
+
File.exist?(path) ? File.read(path) : ""
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def escape(text)
|
|
56
|
+
text.to_s.gsub("&", "&").gsub("<", "<")
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/lutaml/openapi.rb
CHANGED
|
@@ -15,6 +15,8 @@ module Lutaml
|
|
|
15
15
|
# Anything unmodeled rides the extensions catch-all — documents
|
|
16
16
|
# round-trip verbatim.
|
|
17
17
|
module Openapi
|
|
18
|
+
autoload :CorpusAudit, "lutaml/openapi/corpus_audit"
|
|
19
|
+
autoload :Spa, "lutaml/openapi/spa"
|
|
18
20
|
autoload :VERSION, "lutaml/openapi/version"
|
|
19
21
|
autoload :Document, "lutaml/openapi/document"
|
|
20
22
|
autoload :Oas30, "lutaml/openapi/oas30"
|
data/rakelib/corpus.rake
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/openapi/corpus_audit"
|
|
4
|
+
|
|
5
|
+
namespace :corpus do
|
|
6
|
+
desc "Audit the vendored OGC corpus: lossless round-trip + coverage matrix"
|
|
7
|
+
task :audit do
|
|
8
|
+
rows = Lutaml::Openapi::CorpusAudit.matrix
|
|
9
|
+
format = "%-46s %-7s lossless=%-5s schemas=%-4d " \
|
|
10
|
+
"documented=%-4d ops=%-3d ops_text=%d"
|
|
11
|
+
rows.each do |r|
|
|
12
|
+
puts format(format, r[:file], r[:openapi], r[:lossless], r[:schemas],
|
|
13
|
+
r[:schemas_documented], r[:operations],
|
|
14
|
+
r[:operations_with_text])
|
|
15
|
+
end
|
|
16
|
+
failures = rows.reject { |r| r[:lossless] }
|
|
17
|
+
unless failures.empty?
|
|
18
|
+
abort "corpus:audit: lossless round-trip failed for " \
|
|
19
|
+
"#{failures.map { |r| r[:file] }.join(', ')}"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
namespace :frontend do
|
|
25
|
+
File.expand_path("frontend/dist", __dir__)
|
|
26
|
+
node_modules = File.expand_path("frontend/node_modules", __dir__)
|
|
27
|
+
|
|
28
|
+
task :install do
|
|
29
|
+
unless File.directory?(node_modules)
|
|
30
|
+
Dir.chdir("frontend") do
|
|
31
|
+
sh "npm install"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
task build: :install do
|
|
37
|
+
Dir.chdir("frontend") { sh "npm run build" }
|
|
38
|
+
end
|
|
39
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lutaml-openapi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-10-28 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: json
|
|
@@ -80,10 +81,26 @@ files:
|
|
|
80
81
|
- PLAN.md
|
|
81
82
|
- README.md
|
|
82
83
|
- Rakefile
|
|
84
|
+
- frontend/dist/app.iife.js
|
|
85
|
+
- frontend/dist/style.css
|
|
86
|
+
- frontend/index.html
|
|
87
|
+
- frontend/package-lock.json
|
|
88
|
+
- frontend/package.json
|
|
89
|
+
- frontend/src/App.vue
|
|
90
|
+
- frontend/src/app.ts
|
|
91
|
+
- frontend/src/components/OverviewView.vue
|
|
92
|
+
- frontend/src/components/PathsView.vue
|
|
93
|
+
- frontend/src/components/SchemasView.vue
|
|
94
|
+
- frontend/src/style.css
|
|
95
|
+
- frontend/tsconfig.json
|
|
96
|
+
- frontend/vite.config.ts
|
|
83
97
|
- lib/lutaml/openapi.rb
|
|
98
|
+
- lib/lutaml/openapi/corpus_audit.rb
|
|
84
99
|
- lib/lutaml/openapi/document.rb
|
|
85
100
|
- lib/lutaml/openapi/oas30.rb
|
|
101
|
+
- lib/lutaml/openapi/spa.rb
|
|
86
102
|
- lib/lutaml/openapi/version.rb
|
|
103
|
+
- rakelib/corpus.rake
|
|
87
104
|
homepage: https://github.com/lutaml/lutaml-openapi
|
|
88
105
|
licenses:
|
|
89
106
|
- BSD-2-Clause
|
|
@@ -91,6 +108,7 @@ metadata:
|
|
|
91
108
|
homepage_uri: https://github.com/lutaml/lutaml-openapi
|
|
92
109
|
source_code_uri: https://github.com/lutaml/lutaml-openapi
|
|
93
110
|
changelog_uri: https://github.com/lutaml/lutaml-openapi/blob/main/CHANGELOG.md
|
|
111
|
+
post_install_message:
|
|
94
112
|
rdoc_options: []
|
|
95
113
|
require_paths:
|
|
96
114
|
- lib
|
|
@@ -105,7 +123,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
105
123
|
- !ruby/object:Gem::Version
|
|
106
124
|
version: '0'
|
|
107
125
|
requirements: []
|
|
108
|
-
rubygems_version: 3.
|
|
126
|
+
rubygems_version: 3.5.22
|
|
127
|
+
signing_key:
|
|
109
128
|
specification_version: 4
|
|
110
129
|
summary: OpenAPI 3.0/3.1 as a lossless lutaml-model
|
|
111
130
|
test_files: []
|