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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7489a2b5deaa74c751691afdf57939c046e29d6a853774462ce5587e05dcbcf3
4
- data.tar.gz: fca882189c10df105bc30df80c5fe34cc0108ca82b87420e638170ec7a62d1e3
3
+ metadata.gz: eea2d4379420899294e19ce129230527927e0af75f6d7f3aad847edc14a1072b
4
+ data.tar.gz: 71831392b43dc29bed8c8da35da7b021eaf2ed2c377e90b0b949e23e8747df43
5
5
  SHA512:
6
- metadata.gz: b27dd39b382a08e4fc1aa58ae1b9803ffbeee75fc3db6e3df0261871ce43d3aabfbe7cc863207b190c5a88e52212fca6f43acd40506d4f82088094a0ffd57ebf
7
- data.tar.gz: d3af571ef80959e85a9c3e7ccaebb0a5e721d94348616a873edd25f6c488c596542ca0a8a836b94917a34bfdbc675cc2dc50f844995d7c5a055fc44348987926
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-07 10:44:46 UTC using RuboCop version 1.86.1.
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: 14
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
- <span v-else-if="isObjectProperty(field.prop)" class="ctrl-static">{"..."}</span>
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
- const fields = ref<BuilderField[]>(props.properties.map(p => createField(p, props.required, props.schema, props.allSchemas)))
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(--text-muted);
735
- background: var(--bg-secondary);
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-muted);
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(--color-primary);
786
- background: var(--color-primary-alpha);
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: 2px;
832
- background: var(--border-light);
833
- border-radius: 1px;
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: false,
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
- if (t === 'array' && prop.itemsType) return `array of ${prop.itemsType}`
20
- if (resolvedTitle && (t === 'object' || t === 'any') && prop.ref) return resolvedTitle
21
- if (prop.format) return `${t} (${prop.format})`
22
- return t
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) return {}
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' | 'definition'>('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' | 'definition') { activePanelTab.value = tab }
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
 
@@ -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">&middot; {{ def.examples.length }} ex</span>
90
90
  <span v-if="def.minProperties != null" class="text-muted">&middot; min {{ def.minProperties }}</span>
91
91
  <span v-if="def.maxProperties != null" class="text-muted">&middot; 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 (.zip)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Jsonschema
5
- VERSION = "0.1.6"
5
+ VERSION = "0.1.8"
6
6
  end
7
7
  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.6
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-07 00:00:00.000000000 Z
11
+ date: 2026-05-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json