lutaml-jsonschema 0.1.16 → 0.1.17
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 +39 -5
- data/frontend/src/__tests__/spaDataCompleteness.test.ts +338 -0
- data/frontend/src/__tests__/useSchemaTypes.test.ts +23 -0
- data/frontend/src/components/DetailPanel.vue +99 -3
- data/frontend/src/composables/useSchemaTypes.ts +13 -3
- data/frontend/src/types.ts +15 -0
- data/lib/lutaml/jsonschema/spa/spa_builder.rb +34 -1
- data/lib/lutaml/jsonschema/spa/spa_definition.rb +28 -0
- data/lib/lutaml/jsonschema/spa/spa_property.rb +2 -0
- data/lib/lutaml/jsonschema/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a510965cb5b2a1815007d9e5ae6dbd587d4ac431b6b5c600324f855e33443ca5
|
|
4
|
+
data.tar.gz: 74c3d962eaabbe06518b32d4366b4ca4f017cef900cc3ce6610c8a3ebbcc705a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 52e34466cd3d104c64746aeecfe69e8c79b44fcb8e75bc3c2005b3a79027838422e480486ae44dc0b9cae2c40fbc1fcba68e8da0950f272e01695d5fe3f53783
|
|
7
|
+
data.tar.gz: 380679dc0b84274ee54ea25fa0860b93e041e98020b17947e9673a5c75a83022f1ee144afc746d31b27650d467fe9b77ac14801f63175ae531e49fac1abe71d2
|
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-27 07:40:36 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
|
|
@@ -11,15 +11,38 @@ Gemspec/RequiredRubyVersion:
|
|
|
11
11
|
Exclude:
|
|
12
12
|
- 'lutaml-jsonschema.gemspec'
|
|
13
13
|
|
|
14
|
-
# Offense count:
|
|
14
|
+
# Offense count: 2
|
|
15
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
16
|
+
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
|
17
|
+
# SupportedStyles: with_first_element, with_fixed_indentation
|
|
18
|
+
Layout/ArrayAlignment:
|
|
19
|
+
Exclude:
|
|
20
|
+
- 'spec/lutaml/spa_builder_spec.rb'
|
|
21
|
+
|
|
22
|
+
# Offense count: 5
|
|
15
23
|
# This cop supports safe autocorrection (--autocorrect).
|
|
16
24
|
# Configuration parameters: EnforcedStyleAlignWith.
|
|
17
25
|
# SupportedStylesAlignWith: either, start_of_block, start_of_line
|
|
18
26
|
Layout/BlockAlignment:
|
|
19
27
|
Exclude:
|
|
20
28
|
- 'lib/lutaml/jsonschema/spa/spa_builder.rb'
|
|
29
|
+
- 'spec/lutaml/spa_builder_spec.rb'
|
|
30
|
+
|
|
31
|
+
# Offense count: 4
|
|
32
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
33
|
+
Layout/BlockEndNewline:
|
|
34
|
+
Exclude:
|
|
35
|
+
- 'spec/lutaml/spa_builder_spec.rb'
|
|
36
|
+
|
|
37
|
+
# Offense count: 8
|
|
38
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
39
|
+
# Configuration parameters: Width, EnforcedStyleAlignWith, AllowedPatterns.
|
|
40
|
+
# SupportedStylesAlignWith: start_of_line, relative_to_receiver
|
|
41
|
+
Layout/IndentationWidth:
|
|
42
|
+
Exclude:
|
|
43
|
+
- 'spec/lutaml/spa_builder_spec.rb'
|
|
21
44
|
|
|
22
|
-
# Offense count:
|
|
45
|
+
# Offense count: 34
|
|
23
46
|
# This cop supports safe autocorrection (--autocorrect).
|
|
24
47
|
# Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
|
|
25
48
|
# URISchemes: http, https
|
|
@@ -62,7 +85,7 @@ Metrics/AbcSize:
|
|
|
62
85
|
- 'lib/lutaml/jsonschema/schema_set.rb'
|
|
63
86
|
- 'lib/lutaml/jsonschema/spa/spa_builder.rb'
|
|
64
87
|
|
|
65
|
-
# Offense count:
|
|
88
|
+
# Offense count: 5
|
|
66
89
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
|
67
90
|
# AllowedMethods: refine
|
|
68
91
|
Metrics/BlockLength:
|
|
@@ -78,7 +101,7 @@ Metrics/CyclomaticComplexity:
|
|
|
78
101
|
- 'lib/lutaml/jsonschema/reference_resolver.rb'
|
|
79
102
|
- 'lib/lutaml/jsonschema/schema_set.rb'
|
|
80
103
|
|
|
81
|
-
# Offense count:
|
|
104
|
+
# Offense count: 18
|
|
82
105
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
|
83
106
|
Metrics/MethodLength:
|
|
84
107
|
Max: 47
|
|
@@ -107,6 +130,17 @@ Naming/PredicateMethod:
|
|
|
107
130
|
Exclude:
|
|
108
131
|
- 'lib/lutaml/jsonschema/schema_set.rb'
|
|
109
132
|
|
|
133
|
+
# Offense count: 7
|
|
134
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
135
|
+
# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods.
|
|
136
|
+
# SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
|
|
137
|
+
# ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
|
|
138
|
+
# FunctionalMethods: let, let!, subject, watch
|
|
139
|
+
# AllowedMethods: lambda, proc, it
|
|
140
|
+
Style/BlockDelimiters:
|
|
141
|
+
Exclude:
|
|
142
|
+
- 'spec/lutaml/spa_builder_spec.rb'
|
|
143
|
+
|
|
110
144
|
# Offense count: 2
|
|
111
145
|
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
112
146
|
Style/IdenticalConditionalBranches:
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
5
|
+
import { createPinia, setActivePinia } from 'pinia'
|
|
6
|
+
import { useSchemaStore } from '../stores/schemaStore'
|
|
7
|
+
import {
|
|
8
|
+
primaryType,
|
|
9
|
+
displayType,
|
|
10
|
+
refLabel,
|
|
11
|
+
humanizeConstraints,
|
|
12
|
+
hasConstraints,
|
|
13
|
+
} from '../composables/useSchemaTypes'
|
|
14
|
+
import { resolveSchemaRef } from '../composables/useDefinitionResolver'
|
|
15
|
+
import { createField, buildDefaultJson } from '../composables/useBuilderField'
|
|
16
|
+
import type { SpaDocument, SpaProperty, SpaDefinition, SpaSchema } from '../types'
|
|
17
|
+
|
|
18
|
+
function prop(overrides: Partial<SpaProperty> = {}): SpaProperty {
|
|
19
|
+
return { name: 'test', ...overrides }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function definition(overrides: Partial<SpaDefinition> = {}): SpaDefinition {
|
|
23
|
+
return { name: 'Test', properties: [], required: [], ...overrides }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function schema(overrides: Partial<SpaSchema> = {}): SpaSchema {
|
|
27
|
+
return { name: 'TestSchema', properties: [], definitions: [], required: [], ...overrides }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Build a SpaDocument that mimics what the Ruby backend outputs
|
|
32
|
+
* for a complex schema like ISO 19115-4 mdj.json.
|
|
33
|
+
*/
|
|
34
|
+
function buildComplexDoc(): SpaDocument {
|
|
35
|
+
const mdIdentifier: SpaDefinition = {
|
|
36
|
+
name: 'MD_Identifier',
|
|
37
|
+
title: 'Identifier',
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: [
|
|
40
|
+
prop({ name: 'code', type: 'string', required: true }),
|
|
41
|
+
prop({ name: 'codeSpace', type: 'string', format: 'uri' }),
|
|
42
|
+
],
|
|
43
|
+
required: ['code'],
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const topicCategory: SpaDefinition = {
|
|
47
|
+
name: 'MD_TopicCategoryCode',
|
|
48
|
+
title: 'Topic Category',
|
|
49
|
+
type: 'string',
|
|
50
|
+
enum: ['farming', 'biota', 'boundaries', 'climatology', 'economy', 'elevation'],
|
|
51
|
+
properties: [],
|
|
52
|
+
required: [],
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const obligationCode: SpaDefinition = {
|
|
56
|
+
name: 'MD_ObligationCode',
|
|
57
|
+
type: 'string',
|
|
58
|
+
enum: ['mandatory', 'optional', 'conditional'],
|
|
59
|
+
properties: [],
|
|
60
|
+
required: [],
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const ciTelephone: SpaDefinition = {
|
|
64
|
+
name: 'CI_Telephone',
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: [
|
|
67
|
+
prop({ name: 'number', type: 'string' }),
|
|
68
|
+
],
|
|
69
|
+
required: [],
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const durationType: SpaDefinition = {
|
|
73
|
+
name: 'DurationType',
|
|
74
|
+
type: 'string',
|
|
75
|
+
format: 'duration',
|
|
76
|
+
pattern: '^P.*$',
|
|
77
|
+
properties: [],
|
|
78
|
+
required: [],
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const constraintUnion: SpaDefinition = {
|
|
82
|
+
name: 'Abstract_ConstraintUnion',
|
|
83
|
+
hasOneOf: true,
|
|
84
|
+
properties: [],
|
|
85
|
+
required: [],
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const mdMetadata: SpaDefinition = {
|
|
89
|
+
name: 'MD_Metadata',
|
|
90
|
+
title: 'Metadata',
|
|
91
|
+
type: 'object',
|
|
92
|
+
description: 'Root metadata object',
|
|
93
|
+
properties: [
|
|
94
|
+
prop({ name: 'name', type: 'string', required: true }),
|
|
95
|
+
prop({
|
|
96
|
+
name: 'identifiers',
|
|
97
|
+
type: 'array',
|
|
98
|
+
itemsType: 'object',
|
|
99
|
+
itemsRef: '#/$defs/MD_Identifier',
|
|
100
|
+
}),
|
|
101
|
+
prop({
|
|
102
|
+
name: 'topicCategory',
|
|
103
|
+
ref: '#/$defs/MD_TopicCategoryCode',
|
|
104
|
+
type: 'string',
|
|
105
|
+
enum: ['farming', 'biota', 'boundaries', 'climatology', 'economy', 'elevation'],
|
|
106
|
+
}),
|
|
107
|
+
prop({
|
|
108
|
+
name: 'contacts',
|
|
109
|
+
type: 'array',
|
|
110
|
+
itemsType: 'object',
|
|
111
|
+
itemsRef: '#/$defs/CI_Contact',
|
|
112
|
+
minItems: 1,
|
|
113
|
+
}),
|
|
114
|
+
],
|
|
115
|
+
required: ['name'],
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const ciContact: SpaDefinition = {
|
|
119
|
+
name: 'CI_Contact',
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: [
|
|
122
|
+
prop({
|
|
123
|
+
name: 'phone',
|
|
124
|
+
type: 'array',
|
|
125
|
+
itemsType: 'object',
|
|
126
|
+
itemsRef: '#/$defs/CI_Telephone',
|
|
127
|
+
}),
|
|
128
|
+
],
|
|
129
|
+
required: [],
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
metadata: { title: 'ISO 19115-4 Metadata' },
|
|
134
|
+
schemas: [{
|
|
135
|
+
name: 'mdj',
|
|
136
|
+
title: 'ISO 19115-4',
|
|
137
|
+
type: 'object',
|
|
138
|
+
properties: [],
|
|
139
|
+
definitions: [
|
|
140
|
+
mdMetadata,
|
|
141
|
+
mdIdentifier,
|
|
142
|
+
topicCategory,
|
|
143
|
+
obligationCode,
|
|
144
|
+
ciContact,
|
|
145
|
+
ciTelephone,
|
|
146
|
+
durationType,
|
|
147
|
+
constraintUnion,
|
|
148
|
+
],
|
|
149
|
+
required: [],
|
|
150
|
+
sourceJson: '{}',
|
|
151
|
+
}],
|
|
152
|
+
searchIndex: [],
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
describe('SPA data completeness for complex schemas', () => {
|
|
157
|
+
let store: ReturnType<typeof useSchemaStore>
|
|
158
|
+
|
|
159
|
+
beforeEach(() => {
|
|
160
|
+
setActivePinia(createPinia())
|
|
161
|
+
store = useSchemaStore()
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
describe('backend data integrity', () => {
|
|
165
|
+
const doc = buildComplexDoc()
|
|
166
|
+
|
|
167
|
+
it('array properties have itemsRef resolved from items.$ref', () => {
|
|
168
|
+
const mdMetadata = doc.schemas[0].definitions.find(d => d.name === 'MD_Metadata')!
|
|
169
|
+
const identifiers = mdMetadata.properties.find(p => p.name === 'identifiers')!
|
|
170
|
+
expect(identifiers.itemsRef).toBe('#/$defs/MD_Identifier')
|
|
171
|
+
expect(identifiers.itemsType).toBe('object')
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('array properties with itemsRef show correct display type', () => {
|
|
175
|
+
const mdMetadata = doc.schemas[0].definitions.find(d => d.name === 'MD_Metadata')!
|
|
176
|
+
const identifiers = mdMetadata.properties.find(p => p.name === 'identifiers')!
|
|
177
|
+
expect(displayType(identifiers)).toBe('array of MD_Identifier')
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('enum definitions carry their enum values', () => {
|
|
181
|
+
const topicCat = doc.schemas[0].definitions.find(d => d.name === 'MD_TopicCategoryCode')!
|
|
182
|
+
expect(topicCat.enum).toHaveLength(6)
|
|
183
|
+
expect(topicCat.enum).toContain('farming')
|
|
184
|
+
expect(topicCat.enum).toContain('elevation')
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('properties resolved from enum definitions carry enum values', () => {
|
|
188
|
+
const mdMetadata = doc.schemas[0].definitions.find(d => d.name === 'MD_Metadata')!
|
|
189
|
+
const topic = mdMetadata.properties.find(p => p.name === 'topicCategory')!
|
|
190
|
+
expect(topic.enum).toHaveLength(6)
|
|
191
|
+
expect(topic.ref).toBe('#/$defs/MD_TopicCategoryCode')
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('format and pattern on non-object definitions are preserved', () => {
|
|
195
|
+
const duration = doc.schemas[0].definitions.find(d => d.name === 'DurationType')!
|
|
196
|
+
expect(duration.format).toBe('duration')
|
|
197
|
+
expect(duration.pattern).toBe('^P.*$')
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('minItems on array properties is preserved', () => {
|
|
201
|
+
const mdMetadata = doc.schemas[0].definitions.find(d => d.name === 'MD_Metadata')!
|
|
202
|
+
const contacts = mdMetadata.properties.find(p => p.name === 'contacts')!
|
|
203
|
+
expect(contacts.minItems).toBe(1)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('nested array itemsRef through definition chain', () => {
|
|
207
|
+
const ciContact = doc.schemas[0].definitions.find(d => d.name === 'CI_Contact')!
|
|
208
|
+
const phone = ciContact.properties.find(p => p.name === 'phone')!
|
|
209
|
+
expect(phone.itemsRef).toBe('#/$defs/CI_Telephone')
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('hasOneOf flag on union definitions', () => {
|
|
213
|
+
const constraint = doc.schemas[0].definitions.find(d => d.name === 'Abstract_ConstraintUnion')!
|
|
214
|
+
expect(constraint.hasOneOf).toBe(true)
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
describe('frontend rendering with store', () => {
|
|
219
|
+
it('store loads complex document and resolves definitions', () => {
|
|
220
|
+
window.SCHEMA_DATA = buildComplexDoc() as any
|
|
221
|
+
store.loadFromWindow()
|
|
222
|
+
|
|
223
|
+
const schemas = store.schemas
|
|
224
|
+
expect(schemas).toHaveLength(1)
|
|
225
|
+
expect(schemas[0].definitions).toHaveLength(8)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('store normalizes $ref → ref on nested definition properties', () => {
|
|
229
|
+
const doc = buildComplexDoc()
|
|
230
|
+
// Simulate backend output where $ref is the JSON key
|
|
231
|
+
const topicProp = doc.schemas[0].definitions[0].properties.find(p => p.name === 'topicCategory')!
|
|
232
|
+
;(topicProp as any).$ref = topicProp.ref
|
|
233
|
+
delete topicProp.ref
|
|
234
|
+
|
|
235
|
+
window.SCHEMA_DATA = doc as any
|
|
236
|
+
store.loadFromWindow()
|
|
237
|
+
|
|
238
|
+
const mdMetadata = store.schemas[0].definitions.find(d => d.name === 'MD_Metadata')!
|
|
239
|
+
const topic = mdMetadata.properties.find(p => p.name === 'topicCategory')!
|
|
240
|
+
expect(topic.ref).toBe('#/$defs/MD_TopicCategoryCode')
|
|
241
|
+
expect((topic as any).$ref).toBeUndefined()
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('definition resolver finds enum definitions', () => {
|
|
245
|
+
const s = schema({
|
|
246
|
+
definitions: [
|
|
247
|
+
definition({ name: 'MD_TopicCategoryCode', type: 'string', enum: ['farming', 'biota'] }),
|
|
248
|
+
],
|
|
249
|
+
})
|
|
250
|
+
const resolved = resolveSchemaRef('#/$defs/MD_TopicCategoryCode', s)
|
|
251
|
+
expect(resolved).not.toBeNull()
|
|
252
|
+
expect(resolved!.enum).toEqual(['farming', 'biota'])
|
|
253
|
+
})
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
describe('type display for complex schema patterns', () => {
|
|
257
|
+
it('refLabel extracts definition name from $defs path', () => {
|
|
258
|
+
expect(refLabel('#/$defs/MD_Identifier')).toBe('MD_Identifier')
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('refLabel handles definitions path', () => {
|
|
262
|
+
expect(refLabel('#/definitions/address')).toBe('address')
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('displayType for array with itemsRef', () => {
|
|
266
|
+
expect(displayType(prop({ type: 'array', itemsRef: '#/$defs/MD_Identifier' }))).toBe('array of MD_Identifier')
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
it('prefers itemsRef label when itemsType is generic object', () => {
|
|
270
|
+
expect(displayType(prop({ type: 'array', itemsType: 'object', itemsRef: '#/$defs/Foo' }))).toBe('array of Foo')
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('uses itemsType when it is a specific primitive type', () => {
|
|
274
|
+
expect(displayType(prop({ type: 'array', itemsType: 'string', itemsRef: '#/$defs/Foo' }))).toBe('array of string')
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it('displayType for array with itemsRef and constraints', () => {
|
|
278
|
+
const p = prop({ type: 'array', itemsRef: '#/$defs/MD_Identifier', minItems: 1, maxItems: 10 })
|
|
279
|
+
expect(displayType(p)).toBe('array of MD_Identifier [ 1 .. 10 ]')
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('displayType for nullable array with itemsRef', () => {
|
|
283
|
+
expect(displayType(prop({ type: 'array,null', itemsRef: '#/$defs/Item' }))).toBe('array of Item | null')
|
|
284
|
+
})
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
describe('builder field creation for complex properties', () => {
|
|
288
|
+
it('creates field for array property with itemsRef', () => {
|
|
289
|
+
const p = prop({ name: 'ids', type: 'array', itemsType: 'object', itemsRef: '#/$defs/MD_Identifier' })
|
|
290
|
+
const s = schema({
|
|
291
|
+
definitions: [definition({ name: 'MD_Identifier', type: 'object', properties: [prop({ name: 'code', type: 'string' })], required: [] })],
|
|
292
|
+
})
|
|
293
|
+
const field = createField(p, [], s)
|
|
294
|
+
expect(field.arrayItems).toHaveLength(1)
|
|
295
|
+
expect(field.resolvedDef).toBeNull()
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
it('creates field for property resolved from enum definition', () => {
|
|
299
|
+
const p = prop({
|
|
300
|
+
name: 'topic',
|
|
301
|
+
ref: '#/$defs/MD_TopicCategoryCode',
|
|
302
|
+
type: 'string',
|
|
303
|
+
enum: ['farming', 'biota', 'boundaries'],
|
|
304
|
+
})
|
|
305
|
+
const s = schema({
|
|
306
|
+
definitions: [definition({ name: 'MD_TopicCategoryCode', type: 'string', enum: ['farming', 'biota', 'boundaries'], properties: [], required: [] })],
|
|
307
|
+
})
|
|
308
|
+
const field = createField(p, [], s)
|
|
309
|
+
// Enum property should have first enum as initial value
|
|
310
|
+
expect(field.rawValue).toBe('farming')
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
it('buildDefaultJson handles array items with itemsRef', () => {
|
|
314
|
+
const props = [
|
|
315
|
+
prop({ name: 'tags', type: 'array', itemsType: 'string' }),
|
|
316
|
+
]
|
|
317
|
+
const json = buildDefaultJson(props)
|
|
318
|
+
expect(json.tags).toEqual([''])
|
|
319
|
+
})
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
describe('constraint chips for complex definitions', () => {
|
|
323
|
+
it('enum definition has constraints detected via property resolver', () => {
|
|
324
|
+
const p = prop({
|
|
325
|
+
name: 'topic',
|
|
326
|
+
type: 'string',
|
|
327
|
+
enum: ['farming', 'biota'],
|
|
328
|
+
ref: '#/$defs/MD_TopicCategoryCode',
|
|
329
|
+
})
|
|
330
|
+
expect(hasConstraints(p)).toBe(true)
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
it('array with itemsRef and minItems has constraints', () => {
|
|
334
|
+
const p = prop({ name: 'items', type: 'array', itemsRef: '#/$defs/X', minItems: 1 })
|
|
335
|
+
expect(hasConstraints(p)).toBe(true)
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
})
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
numberRange,
|
|
18
18
|
stringRange,
|
|
19
19
|
itemsRange,
|
|
20
|
+
refLabel,
|
|
20
21
|
} from '../composables/useSchemaTypes'
|
|
21
22
|
import type { SpaProperty } from '../types'
|
|
22
23
|
|
|
@@ -86,6 +87,28 @@ describe('displayType', () => {
|
|
|
86
87
|
it('shows array without itemsType', () => {
|
|
87
88
|
expect(displayType(prop({ type: 'array' }))).toBe('array')
|
|
88
89
|
})
|
|
90
|
+
|
|
91
|
+
it('shows array with itemsRef when itemsType is missing', () => {
|
|
92
|
+
expect(displayType(prop({ type: 'array', itemsRef: '#/$defs/MD_Identifier' }))).toBe('array of MD_Identifier')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('prefers itemsType over itemsRef when both present', () => {
|
|
96
|
+
expect(displayType(prop({ type: 'array', itemsType: 'string', itemsRef: '#/$defs/SomeDef' }))).toBe('array of string')
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
describe('refLabel', () => {
|
|
101
|
+
it('extracts last segment from $defs ref', () => {
|
|
102
|
+
expect(refLabel('#/$defs/MD_Identifier')).toBe('MD_Identifier')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('extracts last segment from definitions ref', () => {
|
|
106
|
+
expect(refLabel('#/definitions/address')).toBe('address')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('returns last segment for any path', () => {
|
|
110
|
+
expect(refLabel('#/a/b/c')).toBe('c')
|
|
111
|
+
})
|
|
89
112
|
})
|
|
90
113
|
|
|
91
114
|
describe('formatInputType', () => {
|
|
@@ -140,6 +140,30 @@
|
|
|
140
140
|
<span class="meta-label">Additional</span>
|
|
141
141
|
<span class="badge badge-locked-detail">Denied</span>
|
|
142
142
|
</div>
|
|
143
|
+
<div v-if="definitionItem?.format" class="meta-row">
|
|
144
|
+
<span class="meta-label">Format</span>
|
|
145
|
+
<span class="badge badge-format">{{ definitionItem.format }}</span>
|
|
146
|
+
</div>
|
|
147
|
+
<div v-if="definitionItem?.pattern" class="meta-row">
|
|
148
|
+
<span class="meta-label">Pattern</span>
|
|
149
|
+
<span class="font-mono constraint-pattern">{{ definitionItem.pattern }}</span>
|
|
150
|
+
</div>
|
|
151
|
+
<div v-if="definitionItem?.default" class="meta-row">
|
|
152
|
+
<span class="meta-label">Default</span>
|
|
153
|
+
<span class="font-mono">{{ definitionItem.default }}</span>
|
|
154
|
+
</div>
|
|
155
|
+
<div v-if="definitionItem?.minLength != null || definitionItem?.maxLength != null" class="meta-row">
|
|
156
|
+
<span class="meta-label">Length</span>
|
|
157
|
+
<span class="text-secondary">{{ defStringRange }}</span>
|
|
158
|
+
</div>
|
|
159
|
+
<div v-if="definitionItem?.minimum != null || definitionItem?.maximum != null || definitionItem?.exclusiveMinimum != null || definitionItem?.exclusiveMaximum != null" class="meta-row">
|
|
160
|
+
<span class="meta-label">Range</span>
|
|
161
|
+
<span class="text-secondary">{{ defNumberRange }}</span>
|
|
162
|
+
</div>
|
|
163
|
+
<div v-if="definitionItem?.multipleOf != null" class="meta-row">
|
|
164
|
+
<span class="meta-label">Multiple Of</span>
|
|
165
|
+
<span class="text-secondary">{{ definitionItem.multipleOf }}</span>
|
|
166
|
+
</div>
|
|
143
167
|
</div>
|
|
144
168
|
</div>
|
|
145
169
|
|
|
@@ -173,9 +197,12 @@
|
|
|
173
197
|
</div>
|
|
174
198
|
</td>
|
|
175
199
|
</tr>
|
|
176
|
-
<tr v-if="propertyItem.itemsType">
|
|
200
|
+
<tr v-if="propertyItem.itemsType || propertyItem.itemsRef">
|
|
177
201
|
<td class="constraint-key">Items Type</td>
|
|
178
|
-
<td class="constraint-value">
|
|
202
|
+
<td class="constraint-value">
|
|
203
|
+
{{ propertyItem.itemsType || 'object' }}
|
|
204
|
+
<span v-if="propertyItem.itemsRef" class="prop-ref-link" role="button" tabindex="0" @click.stop="navigateToRef(propertyItem.itemsRef)" @keydown.enter="navigateToRef(propertyItem.itemsRef)"> → {{ refName(propertyItem.itemsRef) }}</span>
|
|
205
|
+
</td>
|
|
179
206
|
</tr>
|
|
180
207
|
<tr v-if="propertyItem.uniqueItems">
|
|
181
208
|
<td class="constraint-key">Unique Items</td>
|
|
@@ -204,6 +231,52 @@
|
|
|
204
231
|
</tbody>
|
|
205
232
|
</table>
|
|
206
233
|
</div>
|
|
234
|
+
|
|
235
|
+
<!-- Constraints for definition (enum, pattern, etc.) -->
|
|
236
|
+
<div v-if="definitionItem && hasDefConstraints" class="detail-section">
|
|
237
|
+
<h3 class="detail-heading">Constraints</h3>
|
|
238
|
+
<table class="table constraint-table">
|
|
239
|
+
<tbody>
|
|
240
|
+
<tr v-if="definitionItem.enum?.length">
|
|
241
|
+
<td class="constraint-key">Enum</td>
|
|
242
|
+
<td>
|
|
243
|
+
<div class="enum-values-list">
|
|
244
|
+
<span v-for="e in visibleEnumValues(definitionItem.name, definitionItem.enum)" :key="e" class="enum-value-chip">{{ e }}</span>
|
|
245
|
+
<button v-if="definitionItem.enum.length > 8 && !detailEnumExpanded.has(definitionItem.name)" class="enum-more-btn" @click="toggleDetailEnum(definitionItem.name)">+{{ definitionItem.enum.length - 8 }} more</button>
|
|
246
|
+
</div>
|
|
247
|
+
</td>
|
|
248
|
+
</tr>
|
|
249
|
+
<tr v-if="definitionItem.const">
|
|
250
|
+
<td class="constraint-key">Const</td>
|
|
251
|
+
<td class="constraint-value font-mono">{{ definitionItem.const }}</td>
|
|
252
|
+
</tr>
|
|
253
|
+
<tr v-if="definitionItem.pattern">
|
|
254
|
+
<td class="constraint-key">Pattern</td>
|
|
255
|
+
<td class="constraint-value font-mono constraint-pattern">{{ definitionItem.pattern }}</td>
|
|
256
|
+
</tr>
|
|
257
|
+
<tr v-if="definitionItem.minimum != null || definitionItem.maximum != null || definitionItem.exclusiveMinimum != null || definitionItem.exclusiveMaximum != null">
|
|
258
|
+
<td class="constraint-key">Range</td>
|
|
259
|
+
<td class="constraint-value">{{ defNumberRange }}</td>
|
|
260
|
+
</tr>
|
|
261
|
+
<tr v-if="definitionItem.minLength != null || definitionItem.maxLength != null">
|
|
262
|
+
<td class="constraint-key">Length</td>
|
|
263
|
+
<td class="constraint-value">{{ defStringRange }}</td>
|
|
264
|
+
</tr>
|
|
265
|
+
<tr v-if="definitionItem.multipleOf != null">
|
|
266
|
+
<td class="constraint-key">Multiple Of</td>
|
|
267
|
+
<td class="constraint-value">{{ definitionItem.multipleOf }}</td>
|
|
268
|
+
</tr>
|
|
269
|
+
<tr v-if="definitionItem.contentMediaType">
|
|
270
|
+
<td class="constraint-key">Content Type</td>
|
|
271
|
+
<td class="constraint-value">{{ definitionItem.contentMediaType }}</td>
|
|
272
|
+
</tr>
|
|
273
|
+
<tr v-if="definitionItem.contentEncoding">
|
|
274
|
+
<td class="constraint-key">Content Encoding</td>
|
|
275
|
+
<td class="constraint-value">{{ definitionItem.contentEncoding }}</td>
|
|
276
|
+
</tr>
|
|
277
|
+
</tbody>
|
|
278
|
+
</table>
|
|
279
|
+
</div>
|
|
207
280
|
</template>
|
|
208
281
|
|
|
209
282
|
<!-- Properties/Definition tab -->
|
|
@@ -231,6 +304,7 @@
|
|
|
231
304
|
<span class="prop-type-badge" :class="propTypeClass(prop.type)">{{ prop.type || 'any' }}</span>
|
|
232
305
|
<span v-if="prop.format" class="prop-format"><{{ prop.format }}></span>
|
|
233
306
|
<span v-if="prop.itemsType" class="prop-format">[{{ prop.itemsType }}]</span>
|
|
307
|
+
<span v-if="prop.itemsRef" class="prop-format">[<span class="prop-ref-link" role="button" tabindex="0" @click.stop="navigateToRef(prop.itemsRef)" @keydown.enter="navigateToRef(prop.itemsRef)">{{ refName(prop.itemsRef) }}</span>]</span>
|
|
234
308
|
</template>
|
|
235
309
|
<span v-if="prop.default != null" class="prop-default">default: {{ prop.default }}</span>
|
|
236
310
|
<span v-if="prop.enum?.length" class="prop-enum">{{ prop.enum.length }} values</span>
|
|
@@ -385,7 +459,7 @@ const hasConstraints = computed(() => {
|
|
|
385
459
|
if (!p) return false
|
|
386
460
|
return p.minimum != null || p.maximum != null ||
|
|
387
461
|
p.minLength != null || p.maxLength != null ||
|
|
388
|
-
p.pattern || p.enum?.length || p.itemsType ||
|
|
462
|
+
p.pattern || p.enum?.length || p.itemsType || p.itemsRef ||
|
|
389
463
|
p.exclusiveMinimum != null || p.exclusiveMaximum != null ||
|
|
390
464
|
p.minItems != null || p.maxItems != null || p.uniqueItems ||
|
|
391
465
|
p.multipleOf != null || p.const != null ||
|
|
@@ -393,6 +467,16 @@ const hasConstraints = computed(() => {
|
|
|
393
467
|
p.additionalProperties === false
|
|
394
468
|
})
|
|
395
469
|
|
|
470
|
+
const hasDefConstraints = computed(() => {
|
|
471
|
+
const d = definitionItem.value
|
|
472
|
+
if (!d) return false
|
|
473
|
+
return d.enum?.length > 0 || !!d.const || !!d.pattern ||
|
|
474
|
+
d.minimum != null || d.maximum != null ||
|
|
475
|
+
d.exclusiveMinimum != null || d.exclusiveMaximum != null ||
|
|
476
|
+
d.minLength != null || d.maxLength != null ||
|
|
477
|
+
d.multipleOf != null || !!d.contentMediaType || !!d.contentEncoding
|
|
478
|
+
})
|
|
479
|
+
|
|
396
480
|
const numberRangeLabel = computed(() => {
|
|
397
481
|
const p = propertyItem.value
|
|
398
482
|
return p ? numberRange(p) ?? '' : ''
|
|
@@ -408,6 +492,18 @@ const itemsRangeLabel = computed(() => {
|
|
|
408
492
|
return p ? itemsRange(p) ?? '' : ''
|
|
409
493
|
})
|
|
410
494
|
|
|
495
|
+
const defNumberRange = computed(() => {
|
|
496
|
+
const d = definitionItem.value
|
|
497
|
+
if (!d) return ''
|
|
498
|
+
return numberRange(d as any) ?? ''
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
const defStringRange = computed(() => {
|
|
502
|
+
const d = definitionItem.value
|
|
503
|
+
if (!d) return ''
|
|
504
|
+
return stringRange(d as any) ?? ''
|
|
505
|
+
})
|
|
506
|
+
|
|
411
507
|
type TabId = 'overview' | 'properties' | 'examples'
|
|
412
508
|
|
|
413
509
|
const examples = computed(() => {
|
|
@@ -15,6 +15,12 @@ export function isNullableType(type?: string): boolean {
|
|
|
15
15
|
return (type || '').split(',').map(s => s.trim()).includes('null')
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/** Extract the definition name from a $ref string (e.g. "#/$defs/MD_Identifier" → "MD_Identifier"). */
|
|
19
|
+
export function refLabel(ref: string): string {
|
|
20
|
+
const parts = ref.split('/')
|
|
21
|
+
return parts[parts.length - 1]
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
/** Whether the type string is a composition indicator from the backend. */
|
|
19
25
|
export function isCompositionType(type?: string): boolean {
|
|
20
26
|
const t = type || ''
|
|
@@ -31,7 +37,10 @@ export function displayType(prop: SpaProperty, resolvedTitle?: string): string {
|
|
|
31
37
|
const suffix = isNullableType(prop.type) ? ' | null' : ''
|
|
32
38
|
if (isCompositionType(t)) return t + suffix
|
|
33
39
|
if (t === 'array') {
|
|
34
|
-
|
|
40
|
+
const itemType = (prop.itemsRef && (!prop.itemsType || prop.itemsType === 'object'))
|
|
41
|
+
? refLabel(prop.itemsRef)
|
|
42
|
+
: prop.itemsType
|
|
43
|
+
let label = itemType ? `array of ${itemType}` : 'array'
|
|
35
44
|
if (prop.minItems != null && prop.maxItems != null) label += ` [ ${prop.minItems} .. ${prop.maxItems} ]`
|
|
36
45
|
else if (prop.minItems != null) label += ` >= ${prop.minItems}`
|
|
37
46
|
else if (prop.maxItems != null) label += ` <= ${prop.maxItems}`
|
|
@@ -144,7 +153,7 @@ export function isObjectProperty(prop: SpaProperty): boolean {
|
|
|
144
153
|
*/
|
|
145
154
|
export function hasConstraints(prop: SpaProperty): boolean {
|
|
146
155
|
return !!(
|
|
147
|
-
|
|
156
|
+
prop.enum?.length ||
|
|
148
157
|
prop.pattern ||
|
|
149
158
|
prop.minimum != null ||
|
|
150
159
|
prop.maximum != null ||
|
|
@@ -160,7 +169,8 @@ export function hasConstraints(prop: SpaProperty): boolean {
|
|
|
160
169
|
prop.exclusiveMinimum != null ||
|
|
161
170
|
prop.exclusiveMaximum != null ||
|
|
162
171
|
prop.contentMediaType ||
|
|
163
|
-
prop.contentEncoding
|
|
172
|
+
prop.contentEncoding ||
|
|
173
|
+
prop.itemsRef
|
|
164
174
|
)
|
|
165
175
|
}
|
|
166
176
|
|
data/frontend/src/types.ts
CHANGED
|
@@ -25,6 +25,7 @@ export interface SpaProperty {
|
|
|
25
25
|
minimum?: number
|
|
26
26
|
maximum?: number
|
|
27
27
|
itemsType?: string
|
|
28
|
+
itemsRef?: string
|
|
28
29
|
deprecated?: boolean
|
|
29
30
|
readOnly?: boolean
|
|
30
31
|
writeOnly?: boolean
|
|
@@ -47,6 +48,20 @@ export interface SpaDefinition {
|
|
|
47
48
|
title?: string
|
|
48
49
|
description?: string
|
|
49
50
|
type?: string
|
|
51
|
+
format?: string
|
|
52
|
+
enum?: string[]
|
|
53
|
+
const?: string
|
|
54
|
+
pattern?: string
|
|
55
|
+
default?: string
|
|
56
|
+
minLength?: number
|
|
57
|
+
maxLength?: number
|
|
58
|
+
minimum?: number
|
|
59
|
+
maximum?: number
|
|
60
|
+
exclusiveMinimum?: number
|
|
61
|
+
exclusiveMaximum?: number
|
|
62
|
+
multipleOf?: number
|
|
63
|
+
contentMediaType?: string
|
|
64
|
+
contentEncoding?: string
|
|
50
65
|
properties: SpaProperty[]
|
|
51
66
|
required: string[]
|
|
52
67
|
examples?: string[]
|
|
@@ -163,6 +163,20 @@ module Lutaml
|
|
|
163
163
|
title: s.title,
|
|
164
164
|
description: s.description,
|
|
165
165
|
type: s.type,
|
|
166
|
+
format: s.format,
|
|
167
|
+
enum: s.enum,
|
|
168
|
+
const_value: s.const,
|
|
169
|
+
pattern: s.pattern,
|
|
170
|
+
default: s.default,
|
|
171
|
+
min_length: s.min_length,
|
|
172
|
+
max_length: s.max_length,
|
|
173
|
+
minimum: s.minimum,
|
|
174
|
+
maximum: s.maximum,
|
|
175
|
+
exclusive_minimum: s.exclusive_minimum,
|
|
176
|
+
exclusive_maximum: s.exclusive_maximum,
|
|
177
|
+
multiple_of: s.multiple_of,
|
|
178
|
+
content_type: s.content_type,
|
|
179
|
+
content_encoding: s.content_encoding,
|
|
166
180
|
properties: properties,
|
|
167
181
|
required: all_required,
|
|
168
182
|
examples: s.examples,
|
|
@@ -195,6 +209,7 @@ module Lutaml
|
|
|
195
209
|
end
|
|
196
210
|
|
|
197
211
|
prop_ref = resolve_prop_ref(entry.schema)
|
|
212
|
+
items_info = resolve_items_info(resolved, root_schema)
|
|
198
213
|
|
|
199
214
|
SpaProperty.new(
|
|
200
215
|
name: entry.name,
|
|
@@ -211,7 +226,8 @@ module Lutaml
|
|
|
211
226
|
max_length: resolved.max_length,
|
|
212
227
|
minimum: resolved.minimum,
|
|
213
228
|
maximum: resolved.maximum,
|
|
214
|
-
items_type:
|
|
229
|
+
items_type: items_info[:type],
|
|
230
|
+
items_ref: items_info[:ref],
|
|
215
231
|
deprecated: resolved.deprecated,
|
|
216
232
|
read_only: resolved.read_only,
|
|
217
233
|
write_only: resolved.write_only,
|
|
@@ -230,6 +246,23 @@ module Lutaml
|
|
|
230
246
|
)
|
|
231
247
|
end
|
|
232
248
|
|
|
249
|
+
def resolve_items_info(resolved, root_schema)
|
|
250
|
+
items = resolved.items
|
|
251
|
+
return { type: nil, ref: nil } unless items
|
|
252
|
+
|
|
253
|
+
if items.dollar_ref
|
|
254
|
+
ref = items.dollar_ref
|
|
255
|
+
resolved_items = @schema_set.resolve_ref(ref, root_schema)
|
|
256
|
+
if resolved_items
|
|
257
|
+
{ type: resolved_items.type || resolved_items.title, ref: ref }
|
|
258
|
+
else
|
|
259
|
+
{ type: nil, ref: ref }
|
|
260
|
+
end
|
|
261
|
+
else
|
|
262
|
+
{ type: items.type, ref: nil }
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
233
266
|
def resolve_prop_ref(schema)
|
|
234
267
|
return schema.dollar_ref if schema.dollar_ref
|
|
235
268
|
|
|
@@ -8,6 +8,20 @@ module Lutaml
|
|
|
8
8
|
attribute :title, :string
|
|
9
9
|
attribute :description, :string
|
|
10
10
|
attribute :type, :string
|
|
11
|
+
attribute :format, :string
|
|
12
|
+
attribute :enum, :string, collection: true
|
|
13
|
+
attribute :const_value, :string
|
|
14
|
+
attribute :pattern, :string
|
|
15
|
+
attribute :default, :string
|
|
16
|
+
attribute :min_length, :integer
|
|
17
|
+
attribute :max_length, :integer
|
|
18
|
+
attribute :minimum, :float
|
|
19
|
+
attribute :maximum, :float
|
|
20
|
+
attribute :exclusive_minimum, :float
|
|
21
|
+
attribute :exclusive_maximum, :float
|
|
22
|
+
attribute :multiple_of, :float
|
|
23
|
+
attribute :content_type, :string
|
|
24
|
+
attribute :content_encoding, :string
|
|
11
25
|
attribute :properties, SpaProperty, collection: true,
|
|
12
26
|
initialize_empty: true
|
|
13
27
|
attribute :required, :string, collection: true
|
|
@@ -24,6 +38,20 @@ module Lutaml
|
|
|
24
38
|
map "title", to: :title
|
|
25
39
|
map "description", to: :description
|
|
26
40
|
map "type", to: :type
|
|
41
|
+
map "format", to: :format
|
|
42
|
+
map "enum", to: :enum
|
|
43
|
+
map "const", to: :const_value
|
|
44
|
+
map "pattern", to: :pattern
|
|
45
|
+
map "default", to: :default
|
|
46
|
+
map "minLength", to: :min_length
|
|
47
|
+
map "maxLength", to: :max_length
|
|
48
|
+
map "minimum", to: :minimum
|
|
49
|
+
map "maximum", to: :maximum
|
|
50
|
+
map "exclusiveMinimum", to: :exclusive_minimum
|
|
51
|
+
map "exclusiveMaximum", to: :exclusive_maximum
|
|
52
|
+
map "multipleOf", to: :multiple_of
|
|
53
|
+
map "contentMediaType", to: :content_type
|
|
54
|
+
map "contentEncoding", to: :content_encoding
|
|
27
55
|
map "properties", to: :properties
|
|
28
56
|
map "required", to: :required
|
|
29
57
|
map "examples", to: :examples
|
|
@@ -19,6 +19,7 @@ module Lutaml
|
|
|
19
19
|
attribute :minimum, :float
|
|
20
20
|
attribute :maximum, :float
|
|
21
21
|
attribute :items_type, :string
|
|
22
|
+
attribute :items_ref, :string
|
|
22
23
|
attribute :deprecated, :boolean
|
|
23
24
|
attribute :read_only, :boolean
|
|
24
25
|
attribute :write_only, :boolean
|
|
@@ -51,6 +52,7 @@ module Lutaml
|
|
|
51
52
|
map "minimum", to: :minimum
|
|
52
53
|
map "maximum", to: :maximum
|
|
53
54
|
map "itemsType", to: :items_type
|
|
55
|
+
map "itemsRef", to: :items_ref
|
|
54
56
|
map "deprecated", to: :deprecated
|
|
55
57
|
map "readOnly", to: :read_only
|
|
56
58
|
map "writeOnly", to: :write_only
|
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.17
|
|
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-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|
|
@@ -95,6 +95,7 @@ files:
|
|
|
95
95
|
- frontend/public/lutaml-logo-full-light.svg
|
|
96
96
|
- frontend/public/lutaml-logo-light.svg
|
|
97
97
|
- frontend/src/App.vue
|
|
98
|
+
- frontend/src/__tests__/spaDataCompleteness.test.ts
|
|
98
99
|
- frontend/src/__tests__/useBuilderField.test.ts
|
|
99
100
|
- frontend/src/__tests__/useClipboard.test.ts
|
|
100
101
|
- frontend/src/__tests__/useDefinitionResolver.test.ts
|