lutaml-jsonschema 0.1.6 → 0.1.8
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/.rubocop_todo.yml +2 -2
- data/frontend/src/components/AppSidebar.vue +2 -1
- data/frontend/src/components/SchemaBuilder.vue +124 -12
- data/frontend/src/composables/useBuilderField.ts +3 -1
- data/frontend/src/composables/useSchemaTypes.ts +59 -5
- data/frontend/src/composables/useSearch.ts +4 -1
- data/frontend/src/stores/uiStore.ts +2 -2
- data/frontend/src/types.ts +5 -0
- data/frontend/src/views/HomeView.vue +5 -1
- data/lib/lutaml/jsonschema/spa/spa_builder.rb +8 -0
- data/lib/lutaml/jsonschema/spa/spa_definition.rb +8 -0
- data/lib/lutaml/jsonschema/spa/spa_search_entry.rb +2 -0
- data/lib/lutaml/jsonschema/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eea2d4379420899294e19ce129230527927e0af75f6d7f3aad847edc14a1072b
|
|
4
|
+
data.tar.gz: 71831392b43dc29bed8c8da35da7b021eaf2ed2c377e90b0b949e23e8747df43
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 52c2b48f8a08223efc1926a18d5260e983d40359c2b5935a2a6962fc0260ff87ddfb7d88d2b4598072d66950ce999b89017afbf2ef9dbbac523b48722cffa336
|
|
7
|
+
data.tar.gz: b9f3b9ae250242f212d5fd59d99497f75143cbca89b079c9a64f476e582cd66ab4cdbd71a9cd8a905f2642b5625071464e86c8de019703119bd2e639f06f7c39
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on 2026-05-
|
|
3
|
+
# on 2026-05-08 02:42:09 UTC using RuboCop version 1.86.1.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
@@ -51,7 +51,7 @@ Lint/IneffectiveAccessModifier:
|
|
|
51
51
|
Exclude:
|
|
52
52
|
- 'lib/lutaml/jsonschema/schema_set.rb'
|
|
53
53
|
|
|
54
|
-
# Offense count:
|
|
54
|
+
# Offense count: 15
|
|
55
55
|
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
|
|
56
56
|
Metrics/AbcSize:
|
|
57
57
|
Exclude:
|
|
@@ -180,7 +180,8 @@ const searchResults = computed(() => {
|
|
|
180
180
|
return schemaStore.searchIndex.filter(entry =>
|
|
181
181
|
entry.name.toLowerCase().includes(q) ||
|
|
182
182
|
(entry.title && entry.title.toLowerCase().includes(q)) ||
|
|
183
|
-
entry.schemaName.toLowerCase().includes(q)
|
|
183
|
+
entry.schemaName.toLowerCase().includes(q) ||
|
|
184
|
+
(entry.description && entry.description.toLowerCase().includes(q)),
|
|
184
185
|
).slice(0, 15)
|
|
185
186
|
})
|
|
186
187
|
|
|
@@ -66,7 +66,9 @@
|
|
|
66
66
|
:value="field.rawValue"
|
|
67
67
|
:disabled="!field.included"
|
|
68
68
|
class="ctrl-number"
|
|
69
|
+
:class="{ 'ctrl-error': fieldError(field.prop.name) }"
|
|
69
70
|
@input="field.rawValue = ($event.target as HTMLInputElement).value"
|
|
71
|
+
@blur="validate(field.prop.name, field.rawValue, field.prop)"
|
|
70
72
|
/>
|
|
71
73
|
|
|
72
74
|
<!-- Number -->
|
|
@@ -76,11 +78,22 @@
|
|
|
76
78
|
:value="field.rawValue"
|
|
77
79
|
:disabled="!field.included"
|
|
78
80
|
class="ctrl-number"
|
|
81
|
+
:class="{ 'ctrl-error': fieldError(field.prop.name) }"
|
|
79
82
|
@input="field.rawValue = ($event.target as HTMLInputElement).value"
|
|
83
|
+
@blur="validate(field.prop.name, field.rawValue, field.prop)"
|
|
80
84
|
/>
|
|
81
85
|
|
|
82
|
-
<!-- Object without $ref -->
|
|
83
|
-
<
|
|
86
|
+
<!-- Object without $ref: editable JSON textarea -->
|
|
87
|
+
<textarea
|
|
88
|
+
v-else-if="isObjectProperty(field.prop)"
|
|
89
|
+
v-model="field.rawValue"
|
|
90
|
+
:disabled="!field.included"
|
|
91
|
+
class="ctrl-textarea"
|
|
92
|
+
:class="{ 'ctrl-error': fieldError(field.prop.name) }"
|
|
93
|
+
rows="2"
|
|
94
|
+
placeholder='{"key": "value"}'
|
|
95
|
+
@blur="validate(field.prop.name, field.rawValue, field.prop)"
|
|
96
|
+
/>
|
|
84
97
|
|
|
85
98
|
<!-- Array with itemsType -->
|
|
86
99
|
<div v-else-if="primaryType(field.prop.type) === 'array'" class="ctrl-array">
|
|
@@ -113,11 +126,17 @@
|
|
|
113
126
|
:value="field.rawValue"
|
|
114
127
|
:disabled="!field.included"
|
|
115
128
|
class="ctrl-text"
|
|
129
|
+
:class="{ 'ctrl-error': fieldError(field.prop.name) }"
|
|
116
130
|
@input="field.rawValue = ($event.target as HTMLInputElement).value"
|
|
131
|
+
@blur="validate(field.prop.name, field.rawValue, field.prop)"
|
|
117
132
|
/>
|
|
118
133
|
</div>
|
|
119
134
|
</div>
|
|
120
135
|
|
|
136
|
+
<div v-if="fieldError(field.prop.name)" class="field-error-hint">
|
|
137
|
+
{{ fieldError(field.prop.name) }}
|
|
138
|
+
</div>
|
|
139
|
+
|
|
121
140
|
<div v-if="field.prop.description" class="field-desc-wrap">
|
|
122
141
|
<div class="field-desc text-secondary" :class="{ 'desc-collapsed': !descExpanded.has(field.prop.name) && field.prop.description.length > 200 }" v-html="renderInlineMarkdown(field.prop.description)"></div>
|
|
123
142
|
<button v-if="field.prop.description.length > 200" class="btn-see-more" @click="toggleDesc(field.prop.name)">
|
|
@@ -215,6 +234,7 @@ import {
|
|
|
215
234
|
isObjectProperty,
|
|
216
235
|
hasConstraints,
|
|
217
236
|
humanizeConstraints,
|
|
237
|
+
validateFieldValue,
|
|
218
238
|
} from '../composables/useSchemaTypes'
|
|
219
239
|
import {
|
|
220
240
|
createField,
|
|
@@ -256,6 +276,7 @@ const descExpanded = ref(new Set<string>())
|
|
|
256
276
|
const enumExpanded = ref(new Set<string>())
|
|
257
277
|
|
|
258
278
|
const MAX_ENUM_SHOW = 8
|
|
279
|
+
const validationErrors = ref<Map<string, string>>(new Map())
|
|
259
280
|
|
|
260
281
|
function toggleDesc(name: string) {
|
|
261
282
|
const s = new Set(descExpanded.value)
|
|
@@ -276,7 +297,19 @@ function toggleEnum(name: string) {
|
|
|
276
297
|
enumExpanded.value = s
|
|
277
298
|
}
|
|
278
299
|
|
|
279
|
-
|
|
300
|
+
function fieldError(name: string): string | undefined {
|
|
301
|
+
return validationErrors.value.get(name)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function validate(name: string, rawValue: string, prop: SpaProperty) {
|
|
305
|
+
const err = validateFieldValue(rawValue, prop)
|
|
306
|
+
const m = new Map(validationErrors.value)
|
|
307
|
+
if (err) m.set(name, err)
|
|
308
|
+
else m.delete(name)
|
|
309
|
+
validationErrors.value = m
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const fields = ref<BuilderField[]>(props.properties.map(p => createField(p, props.required, props.schema, props.allSchemas, depth)))
|
|
280
313
|
|
|
281
314
|
const sortedFields = computed(() => {
|
|
282
315
|
return [...fields.value].sort((a, b) => {
|
|
@@ -618,6 +651,21 @@ async function copyJson() {
|
|
|
618
651
|
border-color: var(--color-primary);
|
|
619
652
|
}
|
|
620
653
|
|
|
654
|
+
.ctrl-error {
|
|
655
|
+
border-color: var(--color-red) !important;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.ctrl-error:focus {
|
|
659
|
+
box-shadow: 0 0 0 2px rgba(179, 31, 36, 0.15);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
.field-error-hint {
|
|
663
|
+
font-size: var(--text-xs);
|
|
664
|
+
color: var(--color-red);
|
|
665
|
+
margin-top: 2px;
|
|
666
|
+
margin-left: 22px;
|
|
667
|
+
}
|
|
668
|
+
|
|
621
669
|
.ctrl-toggle {
|
|
622
670
|
display: flex;
|
|
623
671
|
align-items: center;
|
|
@@ -649,6 +697,28 @@ async function copyJson() {
|
|
|
649
697
|
color: var(--text-muted);
|
|
650
698
|
}
|
|
651
699
|
|
|
700
|
+
.ctrl-textarea {
|
|
701
|
+
width: 100%;
|
|
702
|
+
padding: 3px 8px;
|
|
703
|
+
font-size: var(--text-sm);
|
|
704
|
+
font-family: var(--font-mono);
|
|
705
|
+
background: var(--bg-primary);
|
|
706
|
+
border: 1px solid var(--border-light);
|
|
707
|
+
border-radius: var(--radius-sm);
|
|
708
|
+
color: var(--text-primary);
|
|
709
|
+
resize: vertical;
|
|
710
|
+
min-height: 2em;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
.ctrl-textarea:disabled {
|
|
714
|
+
opacity: 0.35;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
.ctrl-textarea:focus {
|
|
718
|
+
outline: none;
|
|
719
|
+
border-color: var(--color-primary);
|
|
720
|
+
}
|
|
721
|
+
|
|
652
722
|
.chevron {
|
|
653
723
|
font-size: 10px;
|
|
654
724
|
transition: transform var(--transition-fast);
|
|
@@ -731,10 +801,11 @@ async function copyJson() {
|
|
|
731
801
|
|
|
732
802
|
.constraint-chip {
|
|
733
803
|
font-size: 11px;
|
|
734
|
-
color: var(--
|
|
735
|
-
background: var(--
|
|
804
|
+
color: var(--color-primary);
|
|
805
|
+
background: var(--color-primary-alpha);
|
|
736
806
|
padding: 1px 5px;
|
|
737
807
|
border-radius: var(--radius-sm);
|
|
808
|
+
border: 1px solid rgba(91, 156, 212, 0.2);
|
|
738
809
|
}
|
|
739
810
|
|
|
740
811
|
.constraint-chip.chip-pattern {
|
|
@@ -768,30 +839,56 @@ async function copyJson() {
|
|
|
768
839
|
.constraint-chip.chip-locked {
|
|
769
840
|
color: var(--color-orange);
|
|
770
841
|
background: var(--color-orange-alpha);
|
|
842
|
+
border-color: rgba(234, 86, 36, 0.2);
|
|
771
843
|
font-weight: 500;
|
|
772
844
|
}
|
|
773
845
|
|
|
846
|
+
.constraint-chip.chip-default {
|
|
847
|
+
color: var(--text-secondary);
|
|
848
|
+
background: var(--bg-secondary);
|
|
849
|
+
border-color: var(--border-light);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
.constraint-chip.chip-const {
|
|
853
|
+
color: var(--color-primary);
|
|
854
|
+
background: var(--color-primary-alpha);
|
|
855
|
+
border-color: rgba(91, 156, 212, 0.2);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
.constraint-chip.chip-unique {
|
|
859
|
+
color: var(--color-teal);
|
|
860
|
+
background: var(--color-teal-alpha);
|
|
861
|
+
border-color: rgba(96, 195, 167, 0.2);
|
|
862
|
+
}
|
|
863
|
+
|
|
774
864
|
.constraint-chip.chip-example {
|
|
775
865
|
font-style: italic;
|
|
776
|
-
color: var(--text-
|
|
866
|
+
color: var(--text-secondary);
|
|
777
867
|
background: transparent;
|
|
778
868
|
padding: 0;
|
|
869
|
+
border: none;
|
|
779
870
|
margin-right: 0;
|
|
780
871
|
}
|
|
781
872
|
|
|
782
873
|
.example-chip {
|
|
783
874
|
font-size: 10px;
|
|
784
875
|
font-family: var(--font-mono);
|
|
785
|
-
color: var(--
|
|
786
|
-
background:
|
|
876
|
+
color: var(--text-primary);
|
|
877
|
+
background: rgba(28, 25, 23, 0.04);
|
|
787
878
|
padding: 1px 5px;
|
|
788
879
|
border-radius: var(--radius-sm);
|
|
880
|
+
border: 1px solid var(--border-light);
|
|
789
881
|
max-width: 200px;
|
|
790
882
|
overflow: hidden;
|
|
791
883
|
text-overflow: ellipsis;
|
|
792
884
|
white-space: nowrap;
|
|
793
885
|
}
|
|
794
886
|
|
|
887
|
+
:root[data-theme="dark"] .example-chip {
|
|
888
|
+
background: rgba(255, 255, 255, 0.06);
|
|
889
|
+
border-color: var(--border-medium);
|
|
890
|
+
}
|
|
891
|
+
|
|
795
892
|
/* Enum chips */
|
|
796
893
|
.enum-chip {
|
|
797
894
|
font-size: 10px;
|
|
@@ -818,19 +915,29 @@ async function copyJson() {
|
|
|
818
915
|
border: 1px solid var(--border-light);
|
|
819
916
|
border-radius: var(--radius-md);
|
|
820
917
|
background: var(--bg-secondary);
|
|
821
|
-
border-left: 3px solid var(--color-primary);
|
|
822
918
|
position: relative;
|
|
823
919
|
}
|
|
824
920
|
|
|
921
|
+
/* Tree line connector: vertical line from parent to nested section */
|
|
825
922
|
.nested-section::before {
|
|
826
923
|
content: '';
|
|
827
924
|
position: absolute;
|
|
828
925
|
left: -11px;
|
|
829
926
|
top: 0;
|
|
830
927
|
bottom: 0;
|
|
831
|
-
width:
|
|
832
|
-
background: var(--border-
|
|
833
|
-
|
|
928
|
+
width: 1px;
|
|
929
|
+
background: var(--border-medium);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/* Tree line connector: horizontal branch into nested section */
|
|
933
|
+
.nested-section::after {
|
|
934
|
+
content: '';
|
|
935
|
+
position: absolute;
|
|
936
|
+
left: -11px;
|
|
937
|
+
top: 14px;
|
|
938
|
+
width: 10px;
|
|
939
|
+
height: 1px;
|
|
940
|
+
background: var(--border-medium);
|
|
834
941
|
}
|
|
835
942
|
|
|
836
943
|
/* Depth-aware alternating backgrounds */
|
|
@@ -986,6 +1093,11 @@ async function copyJson() {
|
|
|
986
1093
|
text-decoration: underline;
|
|
987
1094
|
}
|
|
988
1095
|
|
|
1096
|
+
.json-block :deep(.jv-row:hover) {
|
|
1097
|
+
background: var(--bg-hover);
|
|
1098
|
+
border-radius: 2px;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
989
1101
|
:root[data-theme="dark"] .json-block :deep(.jv-key) { color: var(--color-primary-light); }
|
|
990
1102
|
:root[data-theme="dark"] .json-block :deep(.jv-string) { color: var(--color-teal); }
|
|
991
1103
|
|
|
@@ -24,17 +24,19 @@ export function createField(
|
|
|
24
24
|
requiredNames: string[],
|
|
25
25
|
schema: SpaSchema,
|
|
26
26
|
allSchemas?: SpaSchema[],
|
|
27
|
+
depth: number = 0,
|
|
27
28
|
): BuilderField {
|
|
28
29
|
const isReq = requiredNames.includes(prop.name) || prop.required === true
|
|
29
30
|
const def = resolveSchemaRef(prop.ref, schema, allSchemas)
|
|
30
31
|
const isArray = primaryType(prop.type) === 'array'
|
|
32
|
+
const shouldExpand = depth === 0 && !!def && def.properties.length <= 8
|
|
31
33
|
|
|
32
34
|
return {
|
|
33
35
|
prop,
|
|
34
36
|
included: isReq,
|
|
35
37
|
isRequired: isReq,
|
|
36
38
|
rawValue: initialValue(prop),
|
|
37
|
-
expanded:
|
|
39
|
+
expanded: shouldExpand,
|
|
38
40
|
resolvedDef: def,
|
|
39
41
|
nestedJson: def ? buildDefaultJson(def.properties) : {},
|
|
40
42
|
arrayItems: isArray ? [arrayDefaultValue(prop.itemsType)] : [],
|
|
@@ -16,10 +16,12 @@ export function primaryType(type?: string): string {
|
|
|
16
16
|
*/
|
|
17
17
|
export function displayType(prop: SpaProperty, resolvedTitle?: string): string {
|
|
18
18
|
const t = primaryType(prop.type)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (prop.
|
|
22
|
-
return
|
|
19
|
+
const isNullable = (prop.type || '').split(',').map(s => s.trim()).includes('null')
|
|
20
|
+
const suffix = isNullable ? ' | null' : ''
|
|
21
|
+
if (t === 'array' && prop.itemsType) return `array of ${prop.itemsType}${suffix}`
|
|
22
|
+
if (resolvedTitle && (t === 'object' || t === 'any') && prop.ref) return resolvedTitle + suffix
|
|
23
|
+
if (prop.format) return `${t} (${prop.format})${suffix}`
|
|
24
|
+
return t + suffix
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -69,6 +71,7 @@ export function initialValue(prop: SpaProperty): string {
|
|
|
69
71
|
case 'number': return '0.0'
|
|
70
72
|
case 'boolean': return 'false'
|
|
71
73
|
case 'string': return formatDefault(prop.format)
|
|
74
|
+
case 'object': return '{}'
|
|
72
75
|
default: return ''
|
|
73
76
|
}
|
|
74
77
|
}
|
|
@@ -158,7 +161,9 @@ export function parsePropertyValue(rawValue: string, prop: SpaProperty): unknown
|
|
|
158
161
|
const n = parseFloat(rawValue)
|
|
159
162
|
return isNaN(n) ? 0 : n
|
|
160
163
|
}
|
|
161
|
-
if (t === 'object' && !prop.ref)
|
|
164
|
+
if (t === 'object' && !prop.ref) {
|
|
165
|
+
try { return JSON.parse(rawValue) } catch { return {} }
|
|
166
|
+
}
|
|
162
167
|
return rawValue
|
|
163
168
|
}
|
|
164
169
|
|
|
@@ -261,3 +266,52 @@ export function humanizeConstraints(prop: SpaProperty): ConstraintChip[] {
|
|
|
261
266
|
|
|
262
267
|
return chips
|
|
263
268
|
}
|
|
269
|
+
|
|
270
|
+
export type ValidationError = string
|
|
271
|
+
|
|
272
|
+
export function validateFieldValue(rawValue: string, prop: SpaProperty): ValidationError | null {
|
|
273
|
+
const t = primaryType(prop.type)
|
|
274
|
+
|
|
275
|
+
if (t === 'string' || t === 'any' || !t) {
|
|
276
|
+
if (prop.minLength != null && rawValue.length < prop.minLength && rawValue.length > 0) {
|
|
277
|
+
return `Min ${prop.minLength} characters`
|
|
278
|
+
}
|
|
279
|
+
if (prop.maxLength != null && rawValue.length > prop.maxLength) {
|
|
280
|
+
return `Max ${prop.maxLength} characters`
|
|
281
|
+
}
|
|
282
|
+
if (prop.pattern && rawValue.length > 0) {
|
|
283
|
+
try {
|
|
284
|
+
if (!new RegExp(prop.pattern).test(rawValue)) {
|
|
285
|
+
return `Must match /${prop.pattern.length > 30 ? prop.pattern.slice(0, 30) + '…' : prop.pattern}/`
|
|
286
|
+
}
|
|
287
|
+
} catch { /* invalid regex, skip */ }
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (t === 'integer' || t === 'number') {
|
|
292
|
+
const n = t === 'integer' ? parseInt(rawValue, 10) : parseFloat(rawValue)
|
|
293
|
+
if (rawValue !== '' && isNaN(n)) return 'Invalid number'
|
|
294
|
+
if (!isNaN(n)) {
|
|
295
|
+
if (prop.minimum != null && n < prop.minimum) return `Must be >= ${prop.minimum}`
|
|
296
|
+
if (prop.maximum != null && n > prop.maximum) return `Must be <= ${prop.maximum}`
|
|
297
|
+
if (prop.exclusiveMinimum != null && n <= prop.exclusiveMinimum) return `Must be > ${prop.exclusiveMinimum}`
|
|
298
|
+
if (prop.exclusiveMaximum != null && n >= prop.exclusiveMaximum) return `Must be < ${prop.exclusiveMaximum}`
|
|
299
|
+
if (prop.multipleOf != null && n % prop.multipleOf !== 0) return `Must be multiple of ${prop.multipleOf}`
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (t === 'array') {
|
|
304
|
+
if (rawValue.length > 0) {
|
|
305
|
+
try {
|
|
306
|
+
const arr = JSON.parse(rawValue)
|
|
307
|
+
if (Array.isArray(arr)) {
|
|
308
|
+
if (prop.minItems != null && arr.length < prop.minItems) return `Min ${prop.minItems} items`
|
|
309
|
+
if (prop.maxItems != null && arr.length > prop.maxItems) return `Max ${prop.maxItems} items`
|
|
310
|
+
if (prop.uniqueItems && new Set(arr).size !== arr.length) return 'Items must be unique'
|
|
311
|
+
}
|
|
312
|
+
} catch { /* not JSON, skip */ }
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return null
|
|
317
|
+
}
|
|
@@ -8,6 +8,7 @@ interface SearchResult {
|
|
|
8
8
|
name: string
|
|
9
9
|
rawName: string
|
|
10
10
|
schemaName: string
|
|
11
|
+
description?: string
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export function useSearch() {
|
|
@@ -26,6 +27,7 @@ export function useSearch() {
|
|
|
26
27
|
name: entry.title || entry.name,
|
|
27
28
|
rawName: entry.name,
|
|
28
29
|
schemaName: entry.schemaName,
|
|
30
|
+
description: entry.description,
|
|
29
31
|
}))
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -41,7 +43,8 @@ export function useSearch() {
|
|
|
41
43
|
|
|
42
44
|
results.value = entries.filter(e =>
|
|
43
45
|
e.name.toLowerCase().includes(q) ||
|
|
44
|
-
e.schemaName.toLowerCase().includes(q)
|
|
46
|
+
e.schemaName.toLowerCase().includes(q) ||
|
|
47
|
+
(e.description && e.description.toLowerCase().includes(q))
|
|
45
48
|
).slice(0, 50)
|
|
46
49
|
|
|
47
50
|
isSearching.value = false
|
|
@@ -8,7 +8,7 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
8
8
|
const resolvedTheme = ref<'light' | 'dark'>('light')
|
|
9
9
|
const sidebarCollapsed = ref(false)
|
|
10
10
|
const detailPanelOpen = ref(false)
|
|
11
|
-
const activePanelTab = ref<'overview' | '
|
|
11
|
+
const activePanelTab = ref<'overview' | 'properties' | 'examples'>('overview')
|
|
12
12
|
const searchOpen = ref(false)
|
|
13
13
|
const expandedSchemaNames = ref<Set<string>>(new Set())
|
|
14
14
|
|
|
@@ -47,7 +47,7 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
47
47
|
function toggleSidebar() { sidebarCollapsed.value = !sidebarCollapsed.value }
|
|
48
48
|
function openDetailPanel() { detailPanelOpen.value = true }
|
|
49
49
|
function closeDetailPanel() { detailPanelOpen.value = false }
|
|
50
|
-
function setPanelTab(tab: 'overview' | '
|
|
50
|
+
function setPanelTab(tab: 'overview' | 'properties' | 'examples') { activePanelTab.value = tab }
|
|
51
51
|
function openSearch() { searchOpen.value = true }
|
|
52
52
|
function closeSearch() { searchOpen.value = false }
|
|
53
53
|
|
data/frontend/src/types.ts
CHANGED
|
@@ -52,6 +52,10 @@ export interface SpaDefinition {
|
|
|
52
52
|
examples?: string[]
|
|
53
53
|
minProperties?: number
|
|
54
54
|
maxProperties?: number
|
|
55
|
+
additionalProperties?: boolean
|
|
56
|
+
hasAllOf?: boolean
|
|
57
|
+
hasAnyOf?: boolean
|
|
58
|
+
hasOneOf?: boolean
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
export interface SpaSchema {
|
|
@@ -77,6 +81,7 @@ export interface SpaSchema {
|
|
|
77
81
|
export interface SpaSearchEntry {
|
|
78
82
|
name: string
|
|
79
83
|
title?: string
|
|
84
|
+
description?: string
|
|
80
85
|
type: string
|
|
81
86
|
schemaName: string
|
|
82
87
|
}
|
|
@@ -89,6 +89,10 @@
|
|
|
89
89
|
<span v-if="def.examples?.length" class="text-muted">· {{ def.examples.length }} ex</span>
|
|
90
90
|
<span v-if="def.minProperties != null" class="text-muted">· min {{ def.minProperties }}</span>
|
|
91
91
|
<span v-if="def.maxProperties != null" class="text-muted">· max {{ def.maxProperties }}</span>
|
|
92
|
+
<span v-if="def.additionalProperties === false" class="badge badge-locked">no additional</span>
|
|
93
|
+
<span v-if="def.hasAllOf" class="badge badge-composition">allOf</span>
|
|
94
|
+
<span v-if="def.hasAnyOf" class="badge badge-composition">anyOf</span>
|
|
95
|
+
<span v-if="def.hasOneOf" class="badge badge-composition">oneOf</span>
|
|
92
96
|
</div>
|
|
93
97
|
<div class="def-card-info">
|
|
94
98
|
<p v-if="def.description" class="def-card-desc text-secondary" v-html="renderInlineMarkdown(def.description)"></p>
|
|
@@ -166,7 +170,7 @@
|
|
|
166
170
|
class="btn btn-outline btn-sm"
|
|
167
171
|
@click="downloadAllSchemas"
|
|
168
172
|
>
|
|
169
|
-
Download All Schemas
|
|
173
|
+
Download All Schemas
|
|
170
174
|
</button>
|
|
171
175
|
</div>
|
|
172
176
|
|
|
@@ -168,6 +168,10 @@ module Lutaml
|
|
|
168
168
|
examples: s.examples,
|
|
169
169
|
min_properties: s.min_properties,
|
|
170
170
|
max_properties: s.max_properties,
|
|
171
|
+
additional_properties: s.additional_properties,
|
|
172
|
+
has_all_of: s.all_of.any?,
|
|
173
|
+
has_any_of: s.any_of.any?,
|
|
174
|
+
has_one_of: s.one_of.any?,
|
|
171
175
|
)
|
|
172
176
|
end
|
|
173
177
|
end
|
|
@@ -232,6 +236,7 @@ module Lutaml
|
|
|
232
236
|
entries = [SpaSearchEntry.new(
|
|
233
237
|
name: spa_schema.name,
|
|
234
238
|
title: spa_schema.title,
|
|
239
|
+
description: spa_schema.description,
|
|
235
240
|
type: "schema",
|
|
236
241
|
schema_name: spa_schema.name,
|
|
237
242
|
)]
|
|
@@ -240,6 +245,7 @@ module Lutaml
|
|
|
240
245
|
entries << SpaSearchEntry.new(
|
|
241
246
|
name: prop.name,
|
|
242
247
|
title: prop.title,
|
|
248
|
+
description: prop.description,
|
|
243
249
|
type: "property",
|
|
244
250
|
schema_name: spa_schema.name,
|
|
245
251
|
)
|
|
@@ -249,6 +255,7 @@ module Lutaml
|
|
|
249
255
|
entries << SpaSearchEntry.new(
|
|
250
256
|
name: defn.name,
|
|
251
257
|
title: defn.title,
|
|
258
|
+
description: defn.description,
|
|
252
259
|
type: "definition",
|
|
253
260
|
schema_name: spa_schema.name,
|
|
254
261
|
)
|
|
@@ -257,6 +264,7 @@ module Lutaml
|
|
|
257
264
|
entries << SpaSearchEntry.new(
|
|
258
265
|
name: prop.name,
|
|
259
266
|
title: prop.title,
|
|
267
|
+
description: prop.description,
|
|
260
268
|
type: "property",
|
|
261
269
|
schema_name: spa_schema.name,
|
|
262
270
|
)
|
|
@@ -14,6 +14,10 @@ module Lutaml
|
|
|
14
14
|
attribute :examples, :string, collection: true
|
|
15
15
|
attribute :min_properties, :integer
|
|
16
16
|
attribute :max_properties, :integer
|
|
17
|
+
attribute :additional_properties, :boolean
|
|
18
|
+
attribute :has_all_of, :boolean
|
|
19
|
+
attribute :has_any_of, :boolean
|
|
20
|
+
attribute :has_one_of, :boolean
|
|
17
21
|
|
|
18
22
|
json do
|
|
19
23
|
map "name", to: :name
|
|
@@ -25,6 +29,10 @@ module Lutaml
|
|
|
25
29
|
map "examples", to: :examples
|
|
26
30
|
map "minProperties", to: :min_properties
|
|
27
31
|
map "maxProperties", to: :max_properties
|
|
32
|
+
map "additionalProperties", to: :additional_properties
|
|
33
|
+
map "hasAllOf", to: :has_all_of
|
|
34
|
+
map "hasAnyOf", to: :has_any_of
|
|
35
|
+
map "hasOneOf", to: :has_one_of
|
|
28
36
|
end
|
|
29
37
|
end
|
|
30
38
|
end
|
|
@@ -6,12 +6,14 @@ module Lutaml
|
|
|
6
6
|
class SpaSearchEntry < Base
|
|
7
7
|
attribute :name, :string
|
|
8
8
|
attribute :title, :string
|
|
9
|
+
attribute :description, :string
|
|
9
10
|
attribute :type, :string
|
|
10
11
|
attribute :schema_name, :string
|
|
11
12
|
|
|
12
13
|
json do
|
|
13
14
|
map "name", to: :name
|
|
14
15
|
map "title", to: :title
|
|
16
|
+
map "description", to: :description
|
|
15
17
|
map "type", to: :type
|
|
16
18
|
map "schemaName", to: :schema_name
|
|
17
19
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lutaml-jsonschema
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|