lutaml-jsonschema 0.1.3 → 0.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fcb6ad4f8e4b98d7610fb58a9556af739f9c3c1ba348de4faf9dfdd83b39f8fc
4
- data.tar.gz: f772f3eb0c0e204d3217ff5c72377ae4a6568daf29015f143eafd7e22d7ed6a6
3
+ metadata.gz: a31e1dc445c583474f10cf52d8468991193679ab428af9d46a0f760492373a2d
4
+ data.tar.gz: 7dfe7fd154e030c64a9670b4d0c694b83e397fa8cff9d08d83222869bbfc1917
5
5
  SHA512:
6
- metadata.gz: 2b8238500b470d461a05119853f1aad9049c2dd92ced3afb7c2c072ccca9e890f0c08a1ac86600dbdd42df47bc21cc2c5893eecf4de4bb0f59e2cdac62897c79
7
- data.tar.gz: 7ec354115ae70763afff045959ba6a2fce37d9f14c1221f254c653a241fc0bae9f2696e3490eb2e5f23b491f140c554b6ec83615d00481529130e9f1af7f636e
6
+ metadata.gz: 2a884595f8021d51474b547a1a06087aec67a5286250ba493f74657f8d9c7e3b8fdfc089c2cad865a90b9fceff984f877f1376c82adceb0928cfc33807b765c7
7
+ data.tar.gz: f01cba83ee80adda6d6bc5ef16494fd23bbee7f0258759f4ea7ddc55305b9c535ab5c2ac4a0be15d8acc5fcbfef8d8f81b5a6655309501d3e2a9cde7f20bb475
@@ -62,6 +62,7 @@
62
62
  >
63
63
  <span class="badge badge-definition-sm">D</span>
64
64
  <span class="tree-item-name">{{ def.title || def.name }}</span>
65
+ <span class="tree-item-count">{{ def.properties.length }}</span>
65
66
  </div>
66
67
  </div>
67
68
  </div>
@@ -342,6 +343,15 @@ function selectDefinition(schemaName: string, defName: string) {
342
343
  text-overflow: ellipsis;
343
344
  }
344
345
 
346
+ .tree-item-count {
347
+ font-size: 10px;
348
+ color: var(--text-muted);
349
+ background: var(--bg-primary);
350
+ padding: 0px 4px;
351
+ border-radius: 2px;
352
+ flex-shrink: 0;
353
+ }
354
+
345
355
  .badge-definition-sm {
346
356
  background: var(--badge-definition-bg);
347
357
  color: var(--badge-definition);
@@ -102,6 +102,46 @@
102
102
  <td class="constraint-key">Items</td>
103
103
  <td>{{ (item as any).property.itemsType }}</td>
104
104
  </tr>
105
+ <tr v-if="(item as any).property.exclusiveMinimum != null">
106
+ <td class="constraint-key">Exclusive Min</td>
107
+ <td>{{ (item as any).property.exclusiveMinimum }}</td>
108
+ </tr>
109
+ <tr v-if="(item as any).property.exclusiveMaximum != null">
110
+ <td class="constraint-key">Exclusive Max</td>
111
+ <td>{{ (item as any).property.exclusiveMaximum }}</td>
112
+ </tr>
113
+ <tr v-if="(item as any).property.minItems != null">
114
+ <td class="constraint-key">Min Items</td>
115
+ <td>{{ (item as any).property.minItems }}</td>
116
+ </tr>
117
+ <tr v-if="(item as any).property.maxItems != null">
118
+ <td class="constraint-key">Max Items</td>
119
+ <td>{{ (item as any).property.maxItems }}</td>
120
+ </tr>
121
+ <tr v-if="(item as any).property.uniqueItems">
122
+ <td class="constraint-key">Unique Items</td>
123
+ <td>Yes</td>
124
+ </tr>
125
+ <tr v-if="(item as any).property.multipleOf != null">
126
+ <td class="constraint-key">Multiple Of</td>
127
+ <td>{{ (item as any).property.multipleOf }}</td>
128
+ </tr>
129
+ <tr v-if="(item as any).property.const != null">
130
+ <td class="constraint-key">Const</td>
131
+ <td class="font-mono">{{ (item as any).property.const }}</td>
132
+ </tr>
133
+ <tr v-if="(item as any).property.contentMediaType">
134
+ <td class="constraint-key">Content Type</td>
135
+ <td>{{ (item as any).property.contentMediaType }}</td>
136
+ </tr>
137
+ <tr v-if="(item as any).property.contentEncoding">
138
+ <td class="constraint-key">Content Encoding</td>
139
+ <td>{{ (item as any).property.contentEncoding }}</td>
140
+ </tr>
141
+ <tr v-if="(item as any).property.additionalProperties === false">
142
+ <td class="constraint-key">Additional Props</td>
143
+ <td>Denied</td>
144
+ </tr>
105
145
  </tbody>
106
146
  </table>
107
147
  </div>
@@ -204,7 +244,11 @@ const hasConstraints = computed(() => {
204
244
  const p = item.value.property
205
245
  return p.minimum != null || p.maximum != null ||
206
246
  p.minLength != null || p.maxLength != null ||
207
- p.pattern || p.enum?.length || p.itemsType
247
+ p.pattern || p.enum?.length || p.itemsType ||
248
+ p.exclusiveMinimum != null || p.exclusiveMaximum != null ||
249
+ p.minItems != null || p.maxItems != null || p.uniqueItems ||
250
+ p.multipleOf != null || p.const != null ||
251
+ p.contentMediaType || p.contentEncoding
208
252
  })
209
253
 
210
254
  type TabId = 'overview' | 'properties'
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="builder-layout">
3
3
  <div class="builder-fields">
4
- <div v-for="field in fields" :key="field.prop.name" class="field-row">
4
+ <div v-for="(field, idx) in sortedFields" :key="field.prop.name" class="field-row" :class="{ 'field-row-alt': idx % 2 === 1 }">
5
5
  <div class="field-main">
6
6
  <input
7
7
  type="checkbox"
@@ -146,7 +146,7 @@
146
146
  </div>
147
147
  </div>
148
148
 
149
- <div v-if="!fields.length" class="empty-hint">
149
+ <div v-if="!sortedFields.length" class="empty-hint">
150
150
  <p class="text-muted">No properties defined.</p>
151
151
  </div>
152
152
  </div>
@@ -203,6 +203,14 @@ const copied = ref(false)
203
203
 
204
204
  const fields = ref<BuilderField[]>(props.properties.map(p => createField(p, props.required, props.schema, props.allSchemas)))
205
205
 
206
+ const sortedFields = computed(() => {
207
+ return [...fields.value].sort((a, b) => {
208
+ if (a.isRequired && !b.isRequired) return -1
209
+ if (!a.isRequired && b.isRequired) return 1
210
+ return 0
211
+ })
212
+ })
213
+
206
214
  function toggleField(field: BuilderField, checked: boolean) {
207
215
  field.included = checked
208
216
  if (!checked) field.expanded = false
@@ -276,6 +284,14 @@ async function copyJson() {
276
284
  background: var(--bg-hover);
277
285
  }
278
286
 
287
+ .field-row-alt {
288
+ background: var(--bg-secondary);
289
+ }
290
+
291
+ .field-row-alt:hover {
292
+ background: var(--bg-hover);
293
+ }
294
+
279
295
  .field-main {
280
296
  display: flex;
281
297
  align-items: center;
@@ -61,6 +61,9 @@ export interface SpaSchema {
61
61
  required: string[]
62
62
  examples?: string[]
63
63
  sourceJson?: string
64
+ $schema?: string
65
+ $id?: string
66
+ additionalProperties?: boolean
64
67
  }
65
68
 
66
69
  export interface SpaSearchEntry {
@@ -6,13 +6,19 @@
6
6
  <div class="schema-header-info">
7
7
  <div class="schema-title-row">
8
8
  <h1>{{ schemaStore.selectedSchema.title || schemaStore.selectedSchema.name }}</h1>
9
- <button
10
- v-if="schemaStore.selectedSchema.sourceJson"
11
- class="btn btn-outline btn-sm"
12
- @click="downloadSchema(schemaStore.selectedSchema)"
13
- >
14
- Download JSON
15
- </button>
9
+ <div class="schema-actions">
10
+ <div class="view-toggle">
11
+ <button class="toggle-btn" :class="{ active: viewMode === 'builder' }" @click="viewMode = 'builder'">Builder</button>
12
+ <button class="toggle-btn" :class="{ active: viewMode === 'source' }" @click="viewMode = 'source'">Source</button>
13
+ </div>
14
+ <button
15
+ v-if="schemaStore.selectedSchema.sourceJson"
16
+ class="btn btn-outline btn-sm"
17
+ @click="downloadSchema(schemaStore.selectedSchema)"
18
+ >
19
+ Download JSON
20
+ </button>
21
+ </div>
16
22
  </div>
17
23
  <span v-if="schemaStore.selectedSchema.title && schemaStore.selectedSchema.title !== schemaStore.selectedSchema.name" class="schema-name-hint font-mono text-muted">{{ schemaStore.selectedSchema.name }}</span>
18
24
  <p v-if="schemaStore.selectedSchema.description" class="schema-desc text-secondary">{{ schemaStore.selectedSchema.description }}</p>
@@ -21,6 +27,11 @@
21
27
  <span class="text-muted">{{ schemaStore.selectedSchema.properties.length }} properties</span>
22
28
  <span v-if="schemaStore.selectedSchema.required.length" class="text-muted">&middot; {{ schemaStore.selectedSchema.required.length }} required</span>
23
29
  <span v-if="schemaStore.selectedSchema.definitions.length" class="text-muted">&middot; {{ schemaStore.selectedSchema.definitions.length }} definitions</span>
30
+ <span v-if="schemaStore.selectedSchema.additionalProperties === false" class="badge badge-locked">no additional properties</span>
31
+ </div>
32
+ <div v-if="schemaStore.selectedSchema.$schema || schemaStore.selectedSchema.$id" class="schema-id-row">
33
+ <span v-if="schemaStore.selectedSchema.$schema" class="meta-id-chip font-mono text-muted" title="JSON Schema version">{{ schemaStore.selectedSchema.$schema }}</span>
34
+ <span v-if="schemaStore.selectedSchema.$id" class="meta-id-chip font-mono text-muted" title="Schema $id">{{ schemaStore.selectedSchema.$id }}</span>
24
35
  </div>
25
36
  <div v-if="schemaStore.selectedSchema.required.length" class="schema-required">
26
37
  <span class="required-label text-muted">Required:</span>
@@ -35,6 +46,8 @@
35
46
  </div>
36
47
  </div>
37
48
 
49
+ <!-- Builder View -->
50
+ <template v-if="viewMode === 'builder'">
38
51
  <!-- Properties -->
39
52
  <div class="schema-section">
40
53
  <SchemaBuilder
@@ -47,7 +60,13 @@
47
60
 
48
61
  <!-- Definitions -->
49
62
  <div v-if="schemaStore.selectedSchema.definitions.length" class="schema-section">
50
- <h2 class="section-heading">Definitions</h2>
63
+ <div class="section-heading-row">
64
+ <h2 class="section-heading">Definitions</h2>
65
+ <div class="section-actions">
66
+ <button class="btn btn-ghost btn-sm" @click="expandAllDefs">Expand All</button>
67
+ <button class="btn btn-ghost btn-sm" @click="collapseAllDefs">Collapse All</button>
68
+ </div>
69
+ </div>
51
70
  <div class="def-list">
52
71
  <div
53
72
  v-for="def in schemaStore.selectedSchema.definitions"
@@ -86,6 +105,17 @@
86
105
  </div>
87
106
  </div>
88
107
  </div>
108
+ </template>
109
+
110
+ <!-- Source JSON View -->
111
+ <div v-if="viewMode === 'source'" class="schema-section">
112
+ <div v-if="schemaStore.selectedSchema.sourceJson" class="source-viewer">
113
+ <pre class="source-pre"><code v-html="highlightedSource"></code></pre>
114
+ </div>
115
+ <div v-else class="source-empty">
116
+ <p class="text-muted">Source JSON Schema not available for this schema.</p>
117
+ </div>
118
+ </div>
89
119
  </div>
90
120
 
91
121
  <!-- Landing Page -->
@@ -132,9 +162,10 @@
132
162
  <span class="badge badge-type-sm">{{ schema.type || 'any' }}</span>
133
163
  </div>
134
164
  <div class="schema-card-stats">
135
- <span>{{ schema.properties.length }} properties</span>
136
- <span>{{ schema.definitions.length }} definitions</span>
165
+ <span>{{ schema.properties.length }} props</span>
166
+ <span>{{ schema.definitions.length }} defs</span>
137
167
  <span v-if="schema.required.length">{{ schema.required.length }} required</span>
168
+ <span v-if="schema.examples?.length">{{ schema.examples.length }} examples</span>
138
169
  </div>
139
170
  </div>
140
171
  </div>
@@ -152,6 +183,38 @@ import type { SpaSchema } from '../types'
152
183
 
153
184
  const schemaStore = useSchemaStore()
154
185
  const expandedDefs = reactive(new Set<string>())
186
+ const viewMode = ref<'builder' | 'source'>('builder')
187
+
188
+ function expandAllDefs() {
189
+ for (const def of schemaStore.selectedSchema?.definitions ?? []) {
190
+ expandedDefs.add(def.name)
191
+ }
192
+ }
193
+
194
+ function collapseAllDefs() {
195
+ expandedDefs.clear()
196
+ }
197
+
198
+ const highlightedSource = computed(() => {
199
+ const src = schemaStore.selectedSchema?.sourceJson
200
+ if (!src) return ''
201
+ return syntaxHighlight(src)
202
+ })
203
+
204
+ function syntaxHighlight(json: string): string {
205
+ return json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
206
+ .replace(/("(\\u[\da-fA-F]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g, (match) => {
207
+ let cls = 'json-number'
208
+ if (/^"/.test(match)) {
209
+ cls = /:$/.test(match) ? 'json-key' : 'json-string'
210
+ } else if (/true|false/.test(match)) {
211
+ cls = 'json-boolean'
212
+ } else if (/null/.test(match)) {
213
+ cls = 'json-null'
214
+ }
215
+ return `<span class="${cls}">${match}</span>`
216
+ })
217
+ }
155
218
 
156
219
  function selectSchema(name: string) {
157
220
  schemaStore.selectSchema(name)
@@ -228,6 +291,44 @@ watch(() => schemaStore.selectedDefinitionName, (name) => {
228
291
  margin: 0;
229
292
  }
230
293
 
294
+ .schema-actions {
295
+ display: flex;
296
+ align-items: center;
297
+ gap: var(--space-2);
298
+ flex-shrink: 0;
299
+ }
300
+
301
+ .view-toggle {
302
+ display: flex;
303
+ border: 1px solid var(--border-medium);
304
+ border-radius: var(--radius-md);
305
+ overflow: hidden;
306
+ }
307
+
308
+ .toggle-btn {
309
+ padding: var(--space-1) var(--space-3);
310
+ font-size: var(--text-xs);
311
+ font-weight: 500;
312
+ color: var(--text-muted);
313
+ background: transparent;
314
+ transition: all var(--transition-fast);
315
+ border: none;
316
+ }
317
+
318
+ .toggle-btn:not(:last-child) {
319
+ border-right: 1px solid var(--border-medium);
320
+ }
321
+
322
+ .toggle-btn.active {
323
+ background: var(--color-primary-alpha);
324
+ color: var(--color-primary);
325
+ }
326
+
327
+ .toggle-btn:hover:not(.active) {
328
+ background: var(--bg-hover);
329
+ color: var(--text-primary);
330
+ }
331
+
231
332
  .schema-name-hint {
232
333
  font-size: var(--text-xs);
233
334
  display: block;
@@ -245,6 +346,34 @@ watch(() => schemaStore.selectedDefinitionName, (name) => {
245
346
  align-items: center;
246
347
  gap: var(--space-2);
247
348
  margin-top: var(--space-2);
349
+ flex-wrap: wrap;
350
+ }
351
+
352
+ .schema-id-row {
353
+ display: flex;
354
+ gap: var(--space-2);
355
+ margin-top: var(--space-2);
356
+ flex-wrap: wrap;
357
+ }
358
+
359
+ .meta-id-chip {
360
+ font-size: 10px;
361
+ background: var(--bg-secondary);
362
+ padding: 2px 6px;
363
+ border-radius: var(--radius-sm);
364
+ max-width: 400px;
365
+ overflow: hidden;
366
+ text-overflow: ellipsis;
367
+ white-space: nowrap;
368
+ }
369
+
370
+ .badge-locked {
371
+ font-size: 10px;
372
+ color: var(--color-orange);
373
+ background: var(--color-orange-alpha);
374
+ padding: 2px 6px;
375
+ border-radius: var(--radius-sm);
376
+ font-weight: 500;
248
377
  }
249
378
 
250
379
  .schema-required {
@@ -331,6 +460,51 @@ watch(() => schemaStore.selectedDefinitionName, (name) => {
331
460
  color: var(--text-primary);
332
461
  }
333
462
 
463
+ .section-heading-row {
464
+ display: flex;
465
+ align-items: center;
466
+ justify-content: space-between;
467
+ margin-bottom: var(--space-4);
468
+ }
469
+
470
+ .section-actions {
471
+ display: flex;
472
+ gap: var(--space-2);
473
+ }
474
+
475
+ /* Source viewer */
476
+ .source-viewer {
477
+ border-radius: var(--radius-lg);
478
+ overflow: hidden;
479
+ }
480
+
481
+ .source-pre {
482
+ background: var(--bg-secondary);
483
+ border: 1px solid var(--border-light);
484
+ border-radius: var(--radius-lg);
485
+ padding: var(--space-4);
486
+ font-size: var(--text-sm);
487
+ line-height: var(--leading-relaxed);
488
+ overflow-x: auto;
489
+ max-height: 70vh;
490
+ overflow-y: auto;
491
+ margin: 0;
492
+ }
493
+
494
+ .source-pre :deep(.json-key) { color: var(--color-primary-dark); }
495
+ .source-pre :deep(.json-string) { color: var(--color-green); }
496
+ .source-pre :deep(.json-number) { color: var(--color-orange); }
497
+ .source-pre :deep(.json-boolean) { color: var(--color-accent); }
498
+ .source-pre :deep(.json-null) { color: var(--text-muted); }
499
+
500
+ :root[data-theme="dark"] .source-pre :deep(.json-key) { color: var(--color-primary-light); }
501
+ :root[data-theme="dark"] .source-pre :deep(.json-string) { color: var(--color-teal); }
502
+
503
+ .source-empty {
504
+ padding: var(--space-8);
505
+ text-align: center;
506
+ }
507
+
334
508
  /* Definitions */
335
509
  .def-list {
336
510
  display: flex;
@@ -53,6 +53,9 @@ module Lutaml
53
53
  required: all_required,
54
54
  examples: schema.examples,
55
55
  source_json: @schema_set.source_json(name) || "",
56
+ dollar_schema: schema.dollar_schema,
57
+ dollar_id: schema.dollar_id,
58
+ additional_properties: schema.additional_properties,
56
59
  )
57
60
  end
58
61
 
@@ -15,6 +15,9 @@ module Lutaml
15
15
  attribute :required, :string, collection: true
16
16
  attribute :examples, :string, collection: true
17
17
  attribute :source_json, :string, default: -> { "" }
18
+ attribute :dollar_schema, :string
19
+ attribute :dollar_id, :string
20
+ attribute :additional_properties, :boolean
18
21
 
19
22
  json do
20
23
  map "name", to: :name
@@ -26,6 +29,9 @@ module Lutaml
26
29
  map "required", to: :required
27
30
  map "examples", to: :examples
28
31
  map "sourceJson", to: :source_json
32
+ map "$schema", to: :dollar_schema
33
+ map "$id", to: :dollar_id
34
+ map "additionalProperties", to: :additional_properties
29
35
  end
30
36
  end
31
37
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Jsonschema
5
- VERSION = "0.1.3"
5
+ VERSION = "0.1.4"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-jsonschema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.