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.
@@ -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("&", "&amp;").gsub("<", "&lt;")
57
+ end
58
+ end
59
+ end
60
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Openapi
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.2"
6
6
  end
7
7
  end
@@ -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"
@@ -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.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: 1980-01-02 00:00:00.000000000 Z
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.6.9
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: []