lutaml-jsonschema 0.1.22 → 0.1.24
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/CLAUDE.md +59 -0
- data/Rakefile +16 -1
- data/benchmark/pipeline_profile.rb +118 -0
- data/frontend/dist/app.iife.js +6 -6
- data/frontend/dist/style.css +1 -1
- data/frontend/src/__tests__/i18n.test.ts +27 -0
- data/frontend/src/__tests__/spaDataCompleteness.test.ts +118 -0
- data/frontend/src/__tests__/useMarkdownLite.test.ts +47 -0
- data/frontend/src/components/AppHeader.vue +6 -5
- data/frontend/src/components/AppSidebar.vue +22 -13
- data/frontend/src/components/DetailPanel.vue +308 -84
- data/frontend/src/components/SchemaBuilder.vue +27 -26
- data/frontend/src/components/SearchModal.vue +4 -3
- data/frontend/src/components/SeeMore.vue +2 -1
- data/frontend/src/composables/useMarkdownLite.ts +79 -3
- data/frontend/src/composables/useSchemaTypes.ts +144 -2
- data/frontend/src/i18n/index.ts +29 -0
- data/frontend/src/i18n/messages.ts +150 -0
- data/frontend/src/stores/schemaStore.ts +2 -0
- data/frontend/src/stores/uiStore.ts +2 -2
- data/frontend/src/style.css +36 -2
- data/frontend/src/types.ts +49 -0
- data/frontend/src/views/HomeView.vue +3 -3
- data/lib/lutaml/jsonschema/cli.rb +154 -15
- data/lib/lutaml/jsonschema/combiner.rb +3 -34
- data/lib/lutaml/jsonschema/configuration.rb +3 -0
- data/lib/lutaml/jsonschema/dialect.rb +49 -0
- data/lib/lutaml/jsonschema/diff.rb +275 -0
- data/lib/lutaml/jsonschema/exports/adoc.rb +173 -0
- data/lib/lutaml/jsonschema/exports/cddl.rb +172 -0
- data/lib/lutaml/jsonschema/exports/openapi.rb +48 -0
- data/lib/lutaml/jsonschema/exports/xsd.rb +183 -0
- data/lib/lutaml/jsonschema/meta_schemas/2019-09/meta/applicator.json +53 -0
- data/lib/lutaml/jsonschema/meta_schemas/2019-09/meta/content.json +14 -0
- data/lib/lutaml/jsonschema/meta_schemas/2019-09/meta/core.json +54 -0
- data/lib/lutaml/jsonschema/meta_schemas/2019-09/meta/format.json +11 -0
- data/lib/lutaml/jsonschema/meta_schemas/2019-09/meta/meta-data.json +34 -0
- data/lib/lutaml/jsonschema/meta_schemas/2019-09/meta/validation.json +95 -0
- data/lib/lutaml/jsonschema/meta_schemas/2019-09/schema.json +42 -0
- data/lib/lutaml/jsonschema/meta_schemas/2020-12/meta/applicator.json +45 -0
- data/lib/lutaml/jsonschema/meta_schemas/2020-12/meta/content.json +14 -0
- data/lib/lutaml/jsonschema/meta_schemas/2020-12/meta/core.json +48 -0
- data/lib/lutaml/jsonschema/meta_schemas/2020-12/meta/format-annotation.json +11 -0
- data/lib/lutaml/jsonschema/meta_schemas/2020-12/meta/meta-data.json +34 -0
- data/lib/lutaml/jsonschema/meta_schemas/2020-12/meta/unevaluated.json +12 -0
- data/lib/lutaml/jsonschema/meta_schemas/2020-12/meta/validation.json +95 -0
- data/lib/lutaml/jsonschema/meta_schemas/2020-12/schema.json +58 -0
- data/lib/lutaml/jsonschema/meta_schemas/NOTICE.md +12 -0
- data/lib/lutaml/jsonschema/meta_schemas/draft-04/schema.json +149 -0
- data/lib/lutaml/jsonschema/meta_schemas/draft-06/schema.json +155 -0
- data/lib/lutaml/jsonschema/meta_schemas/draft-07/schema.json +172 -0
- data/lib/lutaml/jsonschema/property_entry.rb +27 -0
- data/lib/lutaml/jsonschema/reference_resolver.rb +151 -36
- data/lib/lutaml/jsonschema/schema.rb +515 -36
- data/lib/lutaml/jsonschema/schema_set.rb +168 -12
- data/lib/lutaml/jsonschema/schema_store.rb +4 -4
- data/lib/lutaml/jsonschema/spa/generator.rb +4 -2
- data/lib/lutaml/jsonschema/spa/metadata.rb +2 -0
- data/lib/lutaml/jsonschema/spa/spa_builder.rb +212 -98
- data/lib/lutaml/jsonschema/spa/spa_definition.rb +10 -0
- data/lib/lutaml/jsonschema/spa/spa_diff_entry.rb +22 -0
- data/lib/lutaml/jsonschema/spa/spa_document.rb +3 -0
- data/lib/lutaml/jsonschema/spa/spa_property.rb +14 -0
- data/lib/lutaml/jsonschema/spa/spa_schema.rb +26 -0
- data/lib/lutaml/jsonschema/tuple_item.rb +20 -0
- data/lib/lutaml/jsonschema/validator.rb +91 -0
- data/lib/lutaml/jsonschema/version.rb +1 -1
- data/lib/lutaml/jsonschema.rb +12 -0
- data/script/axe_audit +85 -0
- metadata +78 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 229f1667cd05ad90ee3f424ebc68ae1720ec3219eeec3584a1f65d76f09cf90c
|
|
4
|
+
data.tar.gz: 224e628bec6fe5d06986a0dbdee00cb5946b8d39b00e1baa4c3777503db4fbb2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2602ab20233317d4d1ae319739dbc98b86a92598d5decb13a6f5e4e5c3bd0398b69c53698d16c35426599ccd029b565c7a3ceda8546b837d6030d471bcae2c13
|
|
7
|
+
data.tar.gz: f647240a0ce3da5e729da26cbe79e71e02ae3a17bd16b58c71e80bff88711cb8e34d81787caad00522c6494c2ca90c0c0b59966c252f6d96e56e550be6cf355b
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What this is
|
|
6
|
+
|
|
7
|
+
`lutaml-jsonschema` parses JSON Schema documents into lutaml-model objects and generates a single-file SPA documentation site from them. It has two halves that must stay in sync:
|
|
8
|
+
|
|
9
|
+
1. **Ruby gem** (`lib/lutaml/jsonschema/`) — the JSON Schema model and documentation generator.
|
|
10
|
+
2. **Vue 3 frontend** (`frontend/`) — the SPA itself. The built bundle (`frontend/dist/app.iife.js` + `style.css`) gets inlined into a standalone `index.html`.
|
|
11
|
+
|
|
12
|
+
## Commands
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
bundle exec rspec # full suite
|
|
16
|
+
bundle exec rspec spec/lutaml/cli_spec.rb # one file
|
|
17
|
+
bundle exec rspec spec/lutaml/cli_spec.rb:76 # one example
|
|
18
|
+
bundle exec rubocop # lint (-a to autofix; .rubocop_todo.yml holds baselined offenses)
|
|
19
|
+
bundle exec rake # spec + rubocop (what CI runs)
|
|
20
|
+
|
|
21
|
+
bundle exec rake frontend:build # npm install + vite build → frontend/dist
|
|
22
|
+
cd frontend && npm test # vitest (frontend unit tests)
|
|
23
|
+
|
|
24
|
+
bundle exec exe/lutaml-jsonschema spa SCHEMA_FILES... [--diff BASELINE...]
|
|
25
|
+
bundle exec exe/lutaml-jsonschema combine|validate SCHEMA_FILES...
|
|
26
|
+
bundle exec exe/lutaml-jsonschema export SCHEMA_FILES... -f cddl|openapi|xsd|adoc
|
|
27
|
+
bundle exec exe/lutaml-jsonschema diff OLD NEW [--format json]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`frontend/dist/` and `Gemfile.lock` are gitignored. `frontend/dist/` must exist (run `rake frontend:build`) for the `spa` command and for packaging — the gemspec ships it via `Dir.glob("frontend/dist/*")`.
|
|
31
|
+
|
|
32
|
+
## Architecture
|
|
33
|
+
|
|
34
|
+
Data flow: schema files → `SchemaSet.load_from_files` → `Spa::SpaBuilder` → `Spa::SpaDocument` → `to_json` → `Spa::VueInlinedStrategy` (embeds `window.SCHEMA_DATA` + inlined JS/CSS into `index.html`).
|
|
35
|
+
|
|
36
|
+
- **`Base`** — everything inherits from `Lutaml::Model::Serializable`. `Base` also forward-declares `Schema` to break the circular `Schema ↔ PropertyEntry ↔ Link` references.
|
|
37
|
+
- **`Schema`** — the central model of a JSON Schema document. Every `$`-prefixed keyword is a `dollar_`-prefixed attribute (`$schema` → `dollar_schema`) via json mappings. `Schema.from_json` / `to_json` are lutaml-model serialization.
|
|
38
|
+
- **`SchemaSet`** — in-memory named collection of schemas plus `$ref` resolution (local `#/...` pointers and relative `./file.json` refs through `ReferenceResolver`) and validation.
|
|
39
|
+
- **`SchemaStore`** — `SchemaSet` subclass that adds on-disk persistence through **lutaml-store** (`PackageStore`, package shape from `LjrPackageDefinition`).
|
|
40
|
+
- **`Combiner`** — compatibility facade; bundling lives in `SchemaSet#bundle` (hoists definitions, adds a top-level `$ref` property per source schema, rewrites `#/definitions/` → `#/$defs/`).
|
|
41
|
+
- **`Spa::*`** — `SpaBuilder` flattens a `SchemaSet` into a `SpaDocument` (metadata, `SpaSchema` list, flat `search_index` for FlexSearch). `Spa::Generator` renders through an `OutputStrategy`; `VueInlinedStrategy` is the only implementation.
|
|
42
|
+
- **`Configuration`** — YAML config for the SPA (title, theme, appearance…), loaded by `Cli#spa -c`.
|
|
43
|
+
- **`Cli`** — Thor commands: `spa` (generate docs; `--diff` embeds a classified change set), `combine` (merge schemas), `export` (cddl/openapi/xsd/adoc), `diff` (exits 1 on breaking changes), `validate` (meta-schema checks), `version`.
|
|
44
|
+
- **`TupleItem`** — schema-or-boolean wrapper used by `items`/`prefixItems` arrays and composition collections; models that accept `true`/`false` store booleans in companion `*_boolean` attributes and expose `*_value` helpers.
|
|
45
|
+
|
|
46
|
+
## JSON engine: yeptris
|
|
47
|
+
|
|
48
|
+
Two distinct paths, both engine-backed:
|
|
49
|
+
|
|
50
|
+
- **Direct parsing** in this gem (`Cli#combine`, `Cli#validate`, `Schema.serialize_additional_properties`) uses `Yeptris::JSON.load` — strict RFC 8259, exact-`JSON.parse` semantics, raises `Yeptris::JSON::ParseError`. JSON **generation** stays on the stdlib `json` gem (`JSON.pretty_generate`); yeptris's JSON surface is load-only.
|
|
51
|
+
- **Model serialization** (`Schema.from_json`/`to_json`, `Configuration.from_yaml`, lutaml-store I/O) goes through lutaml-model, whose `AdapterResolver` auto-detects yeptris from the bundle and routes both `:json` and `:yaml` to its `YeptrisAdapter`. That adapter re-raises parse failures as `JSON::ParserError`, so error handling must cover **both** exception types — see the rescue in `Cli#validate`.
|
|
52
|
+
- Raw-parse speed depends on the resolved yeptris build shipping a **native materializer for the running Ruby version**; without one it falls back to a pure-Ruby tape engine that is several times slower than `JSON.parse` on large documents (yeptris-ruby#278). Check with `defined?(Yeptris::Native)` (0.6.28.x shipped only Ruby 3.3; 3.4-native starts at 0.6.29.1).
|
|
53
|
+
- The GC-pressure SIGSEGV in the native JSON materializer (yeptris-ruby#279) is fixed in the yeptris **0.6.34 line** (staged pairs made GC-visible); the gemspec floors at `~> 0.6.34`, excluding the crashy 0.6.29.x–0.6.33.x native builds. Env-gated canary: yeptris-ruby#280.
|
|
54
|
+
|
|
55
|
+
## Repo notes
|
|
56
|
+
|
|
57
|
+
- `README.md` is unreplaced gem-template boilerplate — don't treat it as documentation.
|
|
58
|
+
- `TODO.redoc/`, `TODO.refactor/`, `TODO.store/` are untracked, numbered working notes (known gaps and planned refactors), not code. `TODO.sota/` holds the SOTA program (purpose, per-area tasks with done-when criteria, and the goal prompt).
|
|
59
|
+
- CI is metanorma/ci's `generic-rake` workflow; releases run through `.github/workflows/release.yml`.
|
data/Rakefile
CHANGED
|
@@ -9,7 +9,17 @@ require "rubocop/rake_task"
|
|
|
9
9
|
|
|
10
10
|
RuboCop::RakeTask.new
|
|
11
11
|
|
|
12
|
-
task default: %i[spec rubocop]
|
|
12
|
+
task default: %i[spec rubocop perf]
|
|
13
|
+
|
|
14
|
+
namespace :perf do
|
|
15
|
+
desc "Pipeline performance harness with regression caps (TODO.sota/08)"
|
|
16
|
+
task :profile do
|
|
17
|
+
ruby "benchmark/pipeline_profile.rb"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
desc "Run the pipeline performance regression gate"
|
|
22
|
+
task perf: "perf:profile"
|
|
13
23
|
|
|
14
24
|
namespace :frontend do
|
|
15
25
|
desc "Build the Vue frontend"
|
|
@@ -19,6 +29,11 @@ namespace :frontend do
|
|
|
19
29
|
sh "npm run build"
|
|
20
30
|
end
|
|
21
31
|
end
|
|
32
|
+
|
|
33
|
+
desc "axe-core accessibility audit of the generated SPA (needs Chrome)"
|
|
34
|
+
task :audit do
|
|
35
|
+
ruby "script/axe_audit"
|
|
36
|
+
end
|
|
22
37
|
end
|
|
23
38
|
|
|
24
39
|
task :build_frontend do
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Pipeline performance harness (TODO.sota/08).
|
|
4
|
+
#
|
|
5
|
+
# Measures the two budgeted paths on the real-world heroku.json fixture:
|
|
6
|
+
# - Schema.from_json (model construction)
|
|
7
|
+
# - end-to-end SPA generation (SchemaSet + SpaBuilder + inlined HTML)
|
|
8
|
+
#
|
|
9
|
+
# Metrics are reported both absolutely and as a ratio to a stdlib
|
|
10
|
+
# JSON.parse of the same document. The ratio is the machine-independent
|
|
11
|
+
# signal: CI runners vary wildly in absolute speed, but the C parser is
|
|
12
|
+
# a stable per-machine yardstick. Regression caps apply to the ratio.
|
|
13
|
+
#
|
|
14
|
+
# Usage: bundle exec ruby benchmark/pipeline_profile.rb [runs]
|
|
15
|
+
|
|
16
|
+
require "benchmark"
|
|
17
|
+
require "json"
|
|
18
|
+
require "lutaml/jsonschema"
|
|
19
|
+
require "tmpdir"
|
|
20
|
+
|
|
21
|
+
FIXTURES = Dir[File.expand_path("../spec/fixtures/*.json", __dir__)]
|
|
22
|
+
HEROKU = File.expand_path("../spec/fixtures/heroku.json", __dir__)
|
|
23
|
+
DATA = File.read(HEROKU)
|
|
24
|
+
RUNS = (ARGV[0] || 3).to_i
|
|
25
|
+
|
|
26
|
+
# Regression caps, calibrated per environment: the ratio looked
|
|
27
|
+
# machine-independent but is not — CI runners (2 vCPU, shared) inflate the
|
|
28
|
+
# Ruby-vs-C balance ~2x versus a local M-series machine (first real CI run:
|
|
29
|
+
# from_json 5901x, e2e 7853x, suite itself green). Caps catch real
|
|
30
|
+
# regressions (1.3x+ of the environment's own baseline) without flaking.
|
|
31
|
+
# Aspirational budgets are printed separately.
|
|
32
|
+
IN_CI = ["true", "1"].include?(ENV.fetch("CI", nil))
|
|
33
|
+
CAP_FROM_JSON_RATIO, CAP_E2E_RATIO =
|
|
34
|
+
if IN_CI
|
|
35
|
+
[7_500, 10_000]
|
|
36
|
+
else
|
|
37
|
+
[2_600, 5_000]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def median(values)
|
|
41
|
+
values.sort[values.length / 2]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def measure(runs, &)
|
|
45
|
+
Array.new(runs) { Benchmark.realtime(&) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Schema = Lutaml::Jsonschema::Schema
|
|
49
|
+
|
|
50
|
+
# Warmup
|
|
51
|
+
Schema.from_json(DATA)
|
|
52
|
+
JSON.parse(DATA)
|
|
53
|
+
|
|
54
|
+
parse_times = measure(RUNS) { JSON.parse(DATA) }
|
|
55
|
+
from_json_times = measure(RUNS) { Schema.from_json(DATA) }
|
|
56
|
+
|
|
57
|
+
tmp = Dir.mktmpdir
|
|
58
|
+
frontend_built = File.exist?(
|
|
59
|
+
File.join(Lutaml::Jsonschema::Spa::VueInlinedStrategy::FRONTEND_DIST,
|
|
60
|
+
"app.iife.js"),
|
|
61
|
+
)
|
|
62
|
+
generate = lambda do
|
|
63
|
+
set = Lutaml::Jsonschema::SchemaSet.load_from_files(*FIXTURES)
|
|
64
|
+
Lutaml::Jsonschema::Spa::Generator.new(set, tmp).generate
|
|
65
|
+
end
|
|
66
|
+
if frontend_built
|
|
67
|
+
generate.call
|
|
68
|
+
e2e_times = measure(RUNS) { generate.call }
|
|
69
|
+
else
|
|
70
|
+
e2e_times = nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
parse_ms = median(parse_times) * 1000
|
|
74
|
+
from_json_ms = median(from_json_times) * 1000
|
|
75
|
+
e2e_ms = e2e_times && (median(e2e_times) * 1000)
|
|
76
|
+
|
|
77
|
+
from_json_ratio = from_json_ms / parse_ms
|
|
78
|
+
e2e_ratio = e2e_ms && (e2e_ms / parse_ms)
|
|
79
|
+
|
|
80
|
+
per_100kb = from_json_ms / (DATA.bytesize / 100_000.0)
|
|
81
|
+
html_bytes = if e2e_ms
|
|
82
|
+
File.size(File.join(tmp, "index.html"))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
puts "heroku.json: #{DATA.bytesize} bytes, #{FIXTURES.length} fixtures, " \
|
|
86
|
+
"runs=#{RUNS}"
|
|
87
|
+
puts format("JSON.parse (reference): %<ms>10.3f ms", ms: parse_ms)
|
|
88
|
+
puts format("Schema.from_json: %<ms>10.3f ms " \
|
|
89
|
+
"(%<per>0f ms/100KB, %<ref>0fx ref)",
|
|
90
|
+
ms: from_json_ms, per: per_100kb, ref: from_json_ratio)
|
|
91
|
+
if e2e_ms
|
|
92
|
+
puts format("e2e spa generate: %<ms>10.3f ms (%<ref>0fx ref)",
|
|
93
|
+
ms: e2e_ms, ref: e2e_ratio)
|
|
94
|
+
puts format("index.html: %<bytes>10d bytes " \
|
|
95
|
+
"(%<ratio>0.2fx input)",
|
|
96
|
+
bytes: html_bytes, ratio: html_bytes.to_f / DATA.bytesize)
|
|
97
|
+
else
|
|
98
|
+
puts "e2e spa generate: skipped (frontend/dist not built)"
|
|
99
|
+
end
|
|
100
|
+
puts
|
|
101
|
+
puts "environment: #{IN_CI ? 'CI' : 'local'}"
|
|
102
|
+
puts "budgets (aspirational): from_json <= #{DATA.bytesize / 1000} ms " \
|
|
103
|
+
"(100 ms/100KB), e2e <= 1 s/MB"
|
|
104
|
+
puts "regression caps: from_json ratio <= #{CAP_FROM_JSON_RATIO}, " \
|
|
105
|
+
"e2e ratio <= #{CAP_E2E_RATIO}"
|
|
106
|
+
|
|
107
|
+
failures = []
|
|
108
|
+
failures << "from_json ratio #{from_json_ratio.round} > #{CAP_FROM_JSON_RATIO}" \
|
|
109
|
+
if from_json_ratio > CAP_FROM_JSON_RATIO
|
|
110
|
+
failures << "e2e ratio #{e2e_ratio.round} > #{CAP_E2E_RATIO}" \
|
|
111
|
+
if e2e_ratio && e2e_ratio > CAP_E2E_RATIO
|
|
112
|
+
|
|
113
|
+
if failures.empty?
|
|
114
|
+
puts "PERF OK"
|
|
115
|
+
else
|
|
116
|
+
warn "PERF REGRESSION: #{failures.join('; ')}"
|
|
117
|
+
exit 1
|
|
118
|
+
end
|