@api-components/api-type-document 4.2.34 → 4.2.38
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.
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@api-components/api-type-document",
|
|
3
3
|
"description": "A documentation table for type (resource) properties. Works with AMF data model",
|
|
4
|
-
"version": "4.2.
|
|
4
|
+
"version": "4.2.38",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"module": "index.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@advanced-rest-client/arc-marked": "^1.1.0",
|
|
29
29
|
"@advanced-rest-client/markdown-styles": "^3.1.4",
|
|
30
30
|
"@anypoint-web-components/anypoint-button": "^1.2.3",
|
|
31
|
-
"@api-components/amf-helper-mixin": "^4.5.
|
|
31
|
+
"@api-components/amf-helper-mixin": "^4.5.34",
|
|
32
32
|
"@api-components/api-annotation-document": "^4.1.0",
|
|
33
33
|
"@api-components/api-resource-example-document": "^4.3.3",
|
|
34
34
|
"@open-wc/dedupe-mixin": "^1.3.0",
|
|
@@ -39,15 +39,15 @@
|
|
|
39
39
|
"@anypoint-web-components/anypoint-checkbox": "^1.2.2",
|
|
40
40
|
"@anypoint-web-components/anypoint-styles": "^1.0.2",
|
|
41
41
|
"@api-components/api-model-generator": "^0.2.14",
|
|
42
|
-
"@commitlint/cli": "^13.2.
|
|
42
|
+
"@commitlint/cli": "^13.2.1",
|
|
43
43
|
"@commitlint/config-conventional": "^13.2.0",
|
|
44
44
|
"@open-wc/eslint-config": "^4.2.0",
|
|
45
|
-
"@open-wc/testing": "^2.5.
|
|
45
|
+
"@open-wc/testing": "^2.5.32",
|
|
46
46
|
"@web/dev-server": "^0.1.24",
|
|
47
47
|
"@web/test-runner": "^0.13.18",
|
|
48
|
-
"@web/test-runner-playwright": "0.
|
|
49
|
-
"eslint": "^
|
|
50
|
-
"eslint-config-prettier": "^8.
|
|
48
|
+
"@web/test-runner-playwright": "^0.11.0",
|
|
49
|
+
"eslint": "^8.0.0",
|
|
50
|
+
"eslint-config-prettier": "^8.3.0",
|
|
51
51
|
"husky": "^7.0.2",
|
|
52
52
|
"lint-staged": "^11.1.2",
|
|
53
53
|
"sinon": "^11.1.2",
|
package/src/ApiTypeDocument.d.ts
CHANGED
|
@@ -54,6 +54,23 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
|
|
|
54
54
|
selectedMediaType: number;
|
|
55
55
|
// The type after it has been resolved.
|
|
56
56
|
_resolvedType: Object;
|
|
57
|
+
/**
|
|
58
|
+
* Computed properties for object types.
|
|
59
|
+
* This is a reactive property that is recalculated when type, amf, or renderReadOnly changes.
|
|
60
|
+
*/
|
|
61
|
+
_computedProperties: any[] | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Resolved type for examples with all link-target references resolved
|
|
64
|
+
*/
|
|
65
|
+
_resolvedExampleType: Object | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Whether to show the examples section
|
|
68
|
+
*/
|
|
69
|
+
_showExamples: boolean | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Effective media type for examples
|
|
72
|
+
*/
|
|
73
|
+
_exampleMediaType: string | undefined;
|
|
57
74
|
/**
|
|
58
75
|
* Should be set if described properties has a parent type.
|
|
59
76
|
* This is used when recursively iterating over properties.
|
|
@@ -242,6 +259,15 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
|
|
|
242
259
|
*/
|
|
243
260
|
_computeProperties(item: any): any[]|undefined;
|
|
244
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Deeply resolves link-target references in a type and its nested properties.
|
|
264
|
+
* This is essential for rendering complete examples with nested objects.
|
|
265
|
+
*
|
|
266
|
+
* @param type The type to resolve
|
|
267
|
+
* @returns The type with all link-target references resolved
|
|
268
|
+
*/
|
|
269
|
+
_deepResolveType(type: any): any|undefined;
|
|
270
|
+
|
|
245
271
|
/**
|
|
246
272
|
* Computes list values for `andTypes` property.
|
|
247
273
|
* @param items List of OAS' "and" properties
|
package/src/ApiTypeDocument.js
CHANGED
|
@@ -68,6 +68,22 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
|
|
|
68
68
|
selectedMediaType: { type: Number },
|
|
69
69
|
// The type after it has been resolved.
|
|
70
70
|
_resolvedType: { type: Object },
|
|
71
|
+
/**
|
|
72
|
+
* Computed properties from the resolved type
|
|
73
|
+
*/
|
|
74
|
+
_computedProperties: { type: Array },
|
|
75
|
+
/**
|
|
76
|
+
* Resolved type for examples with all link-target references resolved
|
|
77
|
+
*/
|
|
78
|
+
_resolvedExampleType: { type: Object },
|
|
79
|
+
/**
|
|
80
|
+
* Whether to show the examples section
|
|
81
|
+
*/
|
|
82
|
+
_showExamples: { type: Boolean },
|
|
83
|
+
/**
|
|
84
|
+
* Effective media type for examples
|
|
85
|
+
*/
|
|
86
|
+
_exampleMediaType: { type: String },
|
|
71
87
|
/**
|
|
72
88
|
* Should be set if described properties has a parent type.
|
|
73
89
|
* This is used when recursively iterating over properties.
|
|
@@ -325,6 +341,10 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
|
|
|
325
341
|
*/
|
|
326
342
|
this.selectedAnyOf = undefined;
|
|
327
343
|
this.renderReadOnly = false;
|
|
344
|
+
this.noMainExample = false;
|
|
345
|
+
this._hasExamples = false;
|
|
346
|
+
this._renderMainExample = false;
|
|
347
|
+
this._cachedDeepResolvedType = undefined;
|
|
328
348
|
|
|
329
349
|
this._isPropertyReadOnly = this._isPropertyReadOnly.bind(this);
|
|
330
350
|
this.noMediaSelector = false;
|
|
@@ -345,6 +365,37 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
|
|
|
345
365
|
return isScalar ? false : !!(!noMainExample && hasExamples);
|
|
346
366
|
}
|
|
347
367
|
|
|
368
|
+
/**
|
|
369
|
+
* Called when properties change
|
|
370
|
+
* @param {Map} changedProperties Changed properties
|
|
371
|
+
*/
|
|
372
|
+
updated(changedProperties) {
|
|
373
|
+
super.updated(changedProperties);
|
|
374
|
+
|
|
375
|
+
// If amf changed and we have a type, recalculate properties synchronously
|
|
376
|
+
if (changedProperties.has('amf') && this._resolvedType && this.amf) {
|
|
377
|
+
// Cancel any pending debounced calls and recalculate
|
|
378
|
+
this.__typeChangeDebouncer = false;
|
|
379
|
+
this._typeChanged(this._resolvedType);
|
|
380
|
+
// _typeChanged will update _computedProperties, _isGrpcApi, and _deepResolvedType
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// If renderReadOnly changed and we have an object, recalculate properties
|
|
384
|
+
// This is needed because _filterReadOnlyProperties depends on this.renderReadOnly
|
|
385
|
+
if (changedProperties.has('renderReadOnly') && this._resolvedType && this.isObject) {
|
|
386
|
+
this._computedProperties = this._computeProperties(this._resolvedType);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// If noMainExample changed, recalculate whether to render examples
|
|
390
|
+
if (changedProperties.has('noMainExample') && this._resolvedType) {
|
|
391
|
+
this._showExamples = !this.noMainExample && (
|
|
392
|
+
this.renderMediaSelector ||
|
|
393
|
+
this.isObject ||
|
|
394
|
+
this._renderMainExample
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
348
399
|
/**
|
|
349
400
|
* Called when resolved type or amf changed.
|
|
350
401
|
* Creates a debouncer to compute UI values so it's independent of
|
|
@@ -397,9 +448,16 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
|
|
|
397
448
|
} else if (
|
|
398
449
|
this._hasType(type, shapesKey.UnionShape)
|
|
399
450
|
) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
451
|
+
// Check if this is a nullable union (type | null) which should be rendered as scalar
|
|
452
|
+
const nullableCheck = this._checkNullableUnion(type);
|
|
453
|
+
if (nullableCheck && nullableCheck.isNullable) {
|
|
454
|
+
// Treat nullable types as scalar for cleaner rendering
|
|
455
|
+
isScalar = true;
|
|
456
|
+
} else {
|
|
457
|
+
isUnion = true;
|
|
458
|
+
key = this._getAmfKey(shapesKey.anyOf);
|
|
459
|
+
this.unionTypes = this._computeTypes(type, key);
|
|
460
|
+
}
|
|
403
461
|
} else if (this._hasProperty(type, this.ns.w3.shacl.xone)) {
|
|
404
462
|
isOneOf = true;
|
|
405
463
|
key = this._getAmfKey(this.ns.w3.shacl.xone);
|
|
@@ -455,6 +513,32 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
|
|
|
455
513
|
this.isAnd = isAnd;
|
|
456
514
|
this.isOneOf = isOneOf;
|
|
457
515
|
this.isAnyOf = isAnyOf;
|
|
516
|
+
|
|
517
|
+
// Compute properties for objects - this needs to be reactive
|
|
518
|
+
if (isObject) {
|
|
519
|
+
this._computedProperties = this._computeProperties(type);
|
|
520
|
+
} else {
|
|
521
|
+
this._computedProperties = undefined;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Always deep resolve for examples (resolves link-target references for gRPC and similar)
|
|
525
|
+
// This is cheap if there are no link-targets
|
|
526
|
+
if (type) {
|
|
527
|
+
this._resolvedExampleType = this._deepResolveType(type);
|
|
528
|
+
} else {
|
|
529
|
+
this._resolvedExampleType = type;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Determine if we should show the examples section
|
|
533
|
+
// Priority: noMainExample (hide) > renderMediaSelector (show) > isObject (show) > _renderMainExample
|
|
534
|
+
this._showExamples = !this.noMainExample && (
|
|
535
|
+
this.renderMediaSelector || // Need to show the section for the media type selector
|
|
536
|
+
isObject || // Objects can generate examples automatically
|
|
537
|
+
this._renderMainExample // Has explicit examples
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
// Effective media type - use 'application/json' as default for objects without mediaType
|
|
541
|
+
this._exampleMediaType = this.mediaType || (isObject ? 'application/json' : undefined);
|
|
458
542
|
}
|
|
459
543
|
|
|
460
544
|
/**
|
|
@@ -526,18 +610,11 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
|
|
|
526
610
|
if (Array.isArray(item)) {
|
|
527
611
|
[item] = item;
|
|
528
612
|
}
|
|
613
|
+
// For array types in unions, return the array itself instead of unwrapping to items
|
|
614
|
+
// This preserves the "array of" indicator in the UI
|
|
529
615
|
if (this._hasType(item, this.ns.aml.vocabularies.shapes.ArrayShape)) {
|
|
530
616
|
item = this._resolve(item);
|
|
531
|
-
|
|
532
|
-
const items = this._ensureArray(item[itemsKey]);
|
|
533
|
-
if (items && items.length === 1) {
|
|
534
|
-
let result = items[0];
|
|
535
|
-
if (Array.isArray(result)) {
|
|
536
|
-
[result] = result;
|
|
537
|
-
}
|
|
538
|
-
result = this._resolve(result);
|
|
539
|
-
return result;
|
|
540
|
-
}
|
|
617
|
+
return item;
|
|
541
618
|
}
|
|
542
619
|
if (Array.isArray(item)) {
|
|
543
620
|
[item] = item;
|
|
@@ -546,6 +623,91 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
|
|
|
546
623
|
return this._resolve(item);
|
|
547
624
|
}
|
|
548
625
|
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Deeply resolves link-target references in a type for example generation.
|
|
629
|
+
* This ensures that nested objects show their full structure in examples.
|
|
630
|
+
* This is needed for APIs like gRPC where nested types use link-target references.
|
|
631
|
+
*
|
|
632
|
+
* @param {Object} type The type to resolve
|
|
633
|
+
* @return {Object} The deeply resolved type
|
|
634
|
+
*/
|
|
635
|
+
_deepResolveType(type) {
|
|
636
|
+
if (!type || !this.amf) {
|
|
637
|
+
return type;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const resolved = this._resolve(type);
|
|
641
|
+
if (!resolved) {
|
|
642
|
+
return type;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Get properties
|
|
646
|
+
const propertyKey = this._getAmfKey(this.ns.w3.shacl.property);
|
|
647
|
+
const properties = this._ensureArray(resolved[propertyKey]);
|
|
648
|
+
|
|
649
|
+
if (!properties || !properties.length) {
|
|
650
|
+
return resolved;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Create a new type object with deeply resolved properties
|
|
654
|
+
const deepResolved = { ...resolved };
|
|
655
|
+
const linkTargetKey = this._getAmfKey(this.ns.aml.vocabularies.document.linkTarget);
|
|
656
|
+
const rangeKey = this._getAmfKey(this.ns.raml.vocabularies.shapes.range);
|
|
657
|
+
|
|
658
|
+
// Resolve each property's range
|
|
659
|
+
const resolvedProperties = properties.map(prop => {
|
|
660
|
+
const resolvedProp = this._resolve(prop);
|
|
661
|
+
if (!resolvedProp) {
|
|
662
|
+
return prop;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const range = this._ensureArray(resolvedProp[rangeKey])[0];
|
|
666
|
+
if (!range) {
|
|
667
|
+
return resolvedProp;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// If the range has a link-target, resolve it
|
|
671
|
+
if (range[linkTargetKey]) {
|
|
672
|
+
const linkTargetId = this._ensureArray(range[linkTargetKey])[0];
|
|
673
|
+
if (linkTargetId && linkTargetId['@id']) {
|
|
674
|
+
const targetId = linkTargetId['@id'];
|
|
675
|
+
|
|
676
|
+
// Find the target
|
|
677
|
+
const declares = this._computeDeclares(this.amf);
|
|
678
|
+
let target = declares ? this._findById(declares, targetId) : undefined;
|
|
679
|
+
|
|
680
|
+
if (!target) {
|
|
681
|
+
const references = this._computeReferences(this.amf);
|
|
682
|
+
if (references && references.length) {
|
|
683
|
+
for (let i = 0; i < references.length && !target; i++) {
|
|
684
|
+
const refDeclares = this._computeDeclares(references[i]);
|
|
685
|
+
if (refDeclares) {
|
|
686
|
+
target = this._findById(refDeclares, targetId);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// If target found, replace the range with the resolved target
|
|
693
|
+
if (target) {
|
|
694
|
+
const resolvedTarget = this._resolve(target);
|
|
695
|
+
if (resolvedTarget) {
|
|
696
|
+
const newProp = { ...resolvedProp };
|
|
697
|
+
newProp[rangeKey] = [resolvedTarget];
|
|
698
|
+
return newProp;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
return resolvedProp;
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
deepResolved[propertyKey] = resolvedProperties;
|
|
708
|
+
return deepResolved;
|
|
709
|
+
}
|
|
710
|
+
|
|
549
711
|
/**
|
|
550
712
|
* Helper function for the view. Extracts `http://www.w3.org/ns/shacl#property`
|
|
551
713
|
* from the shape model
|
|
@@ -561,16 +723,65 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
|
|
|
561
723
|
return item;
|
|
562
724
|
}
|
|
563
725
|
|
|
726
|
+
// For objects with link-target, we need to find the actual target with properties
|
|
727
|
+
const linkTargetKey = this._getAmfKey(this.ns.aml.vocabularies.document.linkTarget);
|
|
728
|
+
let resolvedItem = item;
|
|
729
|
+
|
|
730
|
+
if (item[linkTargetKey] && this.amf) {
|
|
731
|
+
const linkTargetId = this._ensureArray(item[linkTargetKey])[0];
|
|
732
|
+
if (linkTargetId && linkTargetId['@id']) {
|
|
733
|
+
const targetId = linkTargetId['@id'];
|
|
734
|
+
// Try to find the target in declares
|
|
735
|
+
const declares = this._computeDeclares(this.amf);
|
|
736
|
+
let target = declares ? this._findById(declares, targetId) : undefined;
|
|
737
|
+
|
|
738
|
+
// If not found in declares, search in references
|
|
739
|
+
if (!target) {
|
|
740
|
+
const references = this._computeReferences(this.amf);
|
|
741
|
+
if (references && references.length) {
|
|
742
|
+
for (let i = 0; i < references.length && !target; i++) {
|
|
743
|
+
const refDeclares = this._computeDeclares(references[i]);
|
|
744
|
+
if (refDeclares) {
|
|
745
|
+
target = this._findById(refDeclares, targetId);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// If we found the target and it has properties, use it
|
|
752
|
+
// Don't use the target directly to avoid caching issues
|
|
753
|
+
const propertyKey = this._getAmfKey(this.ns.w3.shacl.property);
|
|
754
|
+
if (target && target[propertyKey]) {
|
|
755
|
+
// Use the target's properties but keep the original item structure
|
|
756
|
+
// This prevents issues with cached __apicResolved flags
|
|
757
|
+
resolvedItem = this._resolve(target);
|
|
758
|
+
// If resolve returned the same object or failed, fallback to standard resolve
|
|
759
|
+
if (!resolvedItem || !resolvedItem[propertyKey]) {
|
|
760
|
+
resolvedItem = this._resolve(item);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Fallback to standard resolve if no link-target or target not found
|
|
767
|
+
if (resolvedItem === item) {
|
|
768
|
+
resolvedItem = this._resolve(item);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (!resolvedItem) {
|
|
772
|
+
return undefined;
|
|
773
|
+
}
|
|
774
|
+
|
|
564
775
|
const propertyKey = this._getAmfKey(this.ns.w3.shacl.property);
|
|
565
|
-
const itemProperties = this._ensureArray(
|
|
776
|
+
const itemProperties = this._ensureArray(resolvedItem[propertyKey]||[])
|
|
566
777
|
const additionalPropertiesKey = this._getAmfKey(this.ns.w3.shacl.additionalPropertiesSchema);
|
|
567
778
|
|
|
568
779
|
// If the item doesn't have additional properties, filter the read-only properties and return
|
|
569
|
-
if (!
|
|
780
|
+
if (!resolvedItem[additionalPropertiesKey]) {
|
|
570
781
|
return this._filterReadOnlyProperties(itemProperties)
|
|
571
782
|
}
|
|
572
783
|
|
|
573
|
-
const additionalPropertiesSchema = this._ensureArray(
|
|
784
|
+
const additionalPropertiesSchema = this._ensureArray(resolvedItem[additionalPropertiesKey])
|
|
574
785
|
|
|
575
786
|
// If the item does have additional properties, ensure they are in an array
|
|
576
787
|
const additionalProperties = this._ensureArray(additionalPropertiesSchema[0][propertyKey] || additionalPropertiesSchema[0])
|
|
@@ -688,7 +899,7 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
|
|
|
688
899
|
* @return {TemplateResult[]|string} Templates for object properties
|
|
689
900
|
*/
|
|
690
901
|
_objectTemplate() {
|
|
691
|
-
const items = this.
|
|
902
|
+
const items = this._computedProperties;
|
|
692
903
|
if (!items || !items.length) {
|
|
693
904
|
return '';
|
|
694
905
|
}
|
|
@@ -924,8 +1135,16 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
|
|
|
924
1135
|
parts +=
|
|
925
1136
|
'code-content-action-button-active, code-wrapper, example-code-wrapper, markdown-html';
|
|
926
1137
|
const mediaTypes = (this.mediaTypes || []);
|
|
1138
|
+
// Use cached values if available, otherwise fallback to computed values
|
|
1139
|
+
const shouldRenderExamples = this._showExamples !== undefined
|
|
1140
|
+
? this._showExamples
|
|
1141
|
+
: this._renderMainExample;
|
|
1142
|
+
const exampleMediaType = this._exampleMediaType !== undefined
|
|
1143
|
+
? this._exampleMediaType
|
|
1144
|
+
: (this.mediaType || (this.isObject ? 'application/json' : undefined));
|
|
1145
|
+
|
|
927
1146
|
return html`<style>${this.styles}</style>
|
|
928
|
-
|
|
1147
|
+
${shouldRenderExamples ? html`<section class="examples">
|
|
929
1148
|
${this.shouldRenderMediaSelector
|
|
930
1149
|
? html`<div class="media-type-selector">
|
|
931
1150
|
<span>Media type:</span>
|
|
@@ -950,18 +1169,18 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
|
|
|
950
1169
|
<api-resource-example-document
|
|
951
1170
|
.amf="${this.amf}"
|
|
952
1171
|
.payloadId="${this.selectedBodyId}"
|
|
953
|
-
.examples="${this._resolvedType}"
|
|
954
|
-
.mediaType="${
|
|
1172
|
+
.examples="${this._resolvedExampleType || this._resolvedType}"
|
|
1173
|
+
.mediaType="${exampleMediaType}"
|
|
955
1174
|
.typeName="${this.parentTypeName}"
|
|
956
1175
|
@has-examples-changed="${this._hasExamplesHandler}"
|
|
957
1176
|
?noauto="${!!this.isScalar}"
|
|
958
1177
|
?noactions="${this.noExamplesActions}"
|
|
959
|
-
?rawOnly="${!
|
|
1178
|
+
?rawOnly="${!exampleMediaType}"
|
|
960
1179
|
?compatibility="${this.compatibility}"
|
|
961
1180
|
exportParts="${parts}"
|
|
962
1181
|
?renderReadOnly="${this.renderReadOnly}"
|
|
963
1182
|
></api-resource-example-document>
|
|
964
|
-
</section
|
|
1183
|
+
</section>` : ''}
|
|
965
1184
|
|
|
966
1185
|
${this.isObject ? this._objectTemplate() : ''}
|
|
967
1186
|
${this.isArray ? this._arrayTemplate() : ''}
|
|
@@ -49,6 +49,15 @@ interface PropertyDocumentMixin extends AmfHelperMixin {
|
|
|
49
49
|
*/
|
|
50
50
|
graph: boolean;
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Checks if a union shape represents a nullable type (union with null).
|
|
54
|
+
* A nullable type is a union of exactly 2 members where one is NilShape.
|
|
55
|
+
*
|
|
56
|
+
* @param range AMF range object (should be UnionShape)
|
|
57
|
+
* @returns Returns {baseType, isNullable: true} if nullable, undefined otherwise
|
|
58
|
+
*/
|
|
59
|
+
_checkNullableUnion(range: any): { baseType: any; isNullable: boolean } | undefined;
|
|
60
|
+
|
|
52
61
|
/**
|
|
53
62
|
* Computes type from a `http://raml.org/vocabularies/shapes#range` object
|
|
54
63
|
*
|
|
@@ -107,6 +107,65 @@ const mxFunction = (base) => {
|
|
|
107
107
|
this._hasMediaType = false;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Checks if a union shape represents a nullable type (union with null).
|
|
112
|
+
* A nullable type is a union of exactly 2 members where one is NilShape
|
|
113
|
+
* and the other is any type (scalar, array, object, etc.).
|
|
114
|
+
*
|
|
115
|
+
* This is specifically for OpenAPI 3.0 nullable: true which AMF converts
|
|
116
|
+
* to union of type + null. This simplifies the rendering to "Type or null"
|
|
117
|
+
* instead of showing a full union selector.
|
|
118
|
+
*
|
|
119
|
+
* @param {any} range AMF range object (should be UnionShape)
|
|
120
|
+
* @return {Object|undefined} Returns {baseType, isNullable: true} if nullable, undefined otherwise
|
|
121
|
+
*/
|
|
122
|
+
_checkNullableUnion(range) {
|
|
123
|
+
if (!range || !this._hasType(range, this.ns.aml.vocabularies.shapes.UnionShape)) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const key = this._getAmfKey(this.ns.aml.vocabularies.shapes.anyOf);
|
|
128
|
+
const unionMembers = this._ensureArray(range[key]);
|
|
129
|
+
|
|
130
|
+
if (!unionMembers || unionMembers.length !== 2) {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Check if one member is NilShape
|
|
135
|
+
let nilIndex = -1;
|
|
136
|
+
let baseTypeIndex = -1;
|
|
137
|
+
|
|
138
|
+
for (let i = 0; i < unionMembers.length; i++) {
|
|
139
|
+
let member = unionMembers[i];
|
|
140
|
+
if (Array.isArray(member)) {
|
|
141
|
+
[member] = member;
|
|
142
|
+
}
|
|
143
|
+
member = this._resolve(member);
|
|
144
|
+
|
|
145
|
+
if (this._hasType(member, this.ns.aml.vocabularies.shapes.NilShape)) {
|
|
146
|
+
nilIndex = i;
|
|
147
|
+
} else {
|
|
148
|
+
baseTypeIndex = i;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// If we found exactly one nil and one non-nil type, it's a nullable
|
|
153
|
+
if (nilIndex !== -1 && baseTypeIndex !== -1) {
|
|
154
|
+
let baseType = unionMembers[baseTypeIndex];
|
|
155
|
+
if (Array.isArray(baseType)) {
|
|
156
|
+
[baseType] = baseType;
|
|
157
|
+
}
|
|
158
|
+
baseType = this._resolve(baseType);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
baseType,
|
|
162
|
+
isNullable: true
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
110
169
|
/**
|
|
111
170
|
* Computes type from a `http://raml.org/vocabularies/shapes#range` object
|
|
112
171
|
*
|
|
@@ -122,6 +181,12 @@ const mxFunction = (base) => {
|
|
|
122
181
|
return this._computeScalarDataType(range);
|
|
123
182
|
}
|
|
124
183
|
if (this._hasType(range, rs.UnionShape)) {
|
|
184
|
+
// Check if this is a nullable union (type | null)
|
|
185
|
+
const nullableCheck = this._checkNullableUnion(range);
|
|
186
|
+
if (nullableCheck && nullableCheck.isNullable) {
|
|
187
|
+
const baseTypeName = this._computeRangeDataType(nullableCheck.baseType);
|
|
188
|
+
return `${baseTypeName} or null`;
|
|
189
|
+
}
|
|
125
190
|
return 'Union';
|
|
126
191
|
}
|
|
127
192
|
if (this._hasType(range, rs.ArrayShape)) {
|
|
@@ -145,9 +210,6 @@ const mxFunction = (base) => {
|
|
|
145
210
|
if (this._hasType(range, rs.TupleShape)) {
|
|
146
211
|
return 'Tuple';
|
|
147
212
|
}
|
|
148
|
-
if (this._hasType(range, rs.UnionShape)) {
|
|
149
|
-
return 'Union';
|
|
150
|
-
}
|
|
151
213
|
if (this._hasType(range, rs.RecursiveShape)) {
|
|
152
214
|
return 'Recursive';
|
|
153
215
|
}
|
|
@@ -313,7 +375,12 @@ const mxFunction = (base) => {
|
|
|
313
375
|
* @return {Boolean}
|
|
314
376
|
*/
|
|
315
377
|
_computeIsUnion(range) {
|
|
316
|
-
|
|
378
|
+
if (!this._hasType(range, this.ns.aml.vocabularies.shapes.UnionShape)) {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
// Check if it's a nullable union (which we don't treat as union for UI)
|
|
382
|
+
const nullableCheck = this._checkNullableUnion(range);
|
|
383
|
+
return !nullableCheck; // Only true if NOT nullable
|
|
317
384
|
}
|
|
318
385
|
|
|
319
386
|
/**
|
|
@@ -325,7 +392,15 @@ const mxFunction = (base) => {
|
|
|
325
392
|
* @return {Boolean}
|
|
326
393
|
*/
|
|
327
394
|
_computeIsObject(range) {
|
|
328
|
-
|
|
395
|
+
if (this._hasType(range, this.ns.w3.shacl.NodeShape)) {
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
// Check if it's a nullable object
|
|
399
|
+
const nullableCheck = this._checkNullableUnion(range);
|
|
400
|
+
if (nullableCheck) {
|
|
401
|
+
return this._hasType(nullableCheck.baseType, this.ns.w3.shacl.NodeShape);
|
|
402
|
+
}
|
|
403
|
+
return false;
|
|
329
404
|
}
|
|
330
405
|
|
|
331
406
|
/**
|
|
@@ -337,7 +412,15 @@ const mxFunction = (base) => {
|
|
|
337
412
|
* @return {Boolean}
|
|
338
413
|
*/
|
|
339
414
|
_computeIsArray(range) {
|
|
340
|
-
|
|
415
|
+
if (this._hasType(range, this.ns.aml.vocabularies.shapes.ArrayShape)) {
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
// Check if it's a nullable array
|
|
419
|
+
const nullableCheck = this._checkNullableUnion(range);
|
|
420
|
+
if (nullableCheck) {
|
|
421
|
+
return this._hasType(nullableCheck.baseType, this.ns.aml.vocabularies.shapes.ArrayShape);
|
|
422
|
+
}
|
|
423
|
+
return false;
|
|
341
424
|
}
|
|
342
425
|
|
|
343
426
|
/**
|
|
@@ -735,7 +735,14 @@ export class PropertyShapeDocument extends PropertyDocumentMixin(LitElement) {
|
|
|
735
735
|
if (!this.isComplex || !this.opened || this.isScalarArray) {
|
|
736
736
|
return '';
|
|
737
737
|
}
|
|
738
|
-
|
|
738
|
+
let range = this._resolve(this.range);
|
|
739
|
+
|
|
740
|
+
// If this is a nullable complex type, extract the base type
|
|
741
|
+
const nullableCheck = this._checkNullableUnion(range);
|
|
742
|
+
if (nullableCheck) {
|
|
743
|
+
range = nullableCheck.baseType;
|
|
744
|
+
}
|
|
745
|
+
|
|
739
746
|
const parentTypeName = this._getParentTypeName();
|
|
740
747
|
return html`<api-type-document
|
|
741
748
|
class="children complex"
|