Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for oneOf / anyOf #229

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions demo/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,76 @@ paths:
- petstore_auth:
- 'write:pets'
- 'read:pets'
'/pet/{petId}/behaviors':
put:
tags:
- pet
summary: Change behaviors
description: Update the pet's behaviors
operationId: updateBehavior
produces:
- application/xml
- application/json
parameters:
- name: petId
in: path
description: ID of pet to return
required: true
type: integer
format: int64
- name: body
in: body
required: true
schema:
oneOf:
- $ref: "#/definitions/CatBehavior"
- $ref: "#/definitions/DogBehavior"
responses:
'200':
description: successful operation
schema:
$ref: '#/definitions/Pet'
'400':
description: Invalid ID supplied
'404':
description: Pet not found
security:
- api_key: []
post:
tags:
- pet
summary: Updates a pet in the store with form data
description: ''
operationId: updatePetWithForm
consumes:
- application/x-www-form-urlencoded
produces:
- application/xml
- application/json
parameters:
- name: petId
in: path
description: ID of pet that needs to be updated
required: true
type: integer
format: int64
- name: name
in: formData
description: Updated name of the pet
required: false
type: string
- name: status
in: formData
description: Updated status of the pet
required: false
type: string
responses:
'405':
description: Invalid input
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
/pet/findByStatus:
get:
tags:
Expand Down Expand Up @@ -653,12 +723,34 @@ definitions:
type: string
message:
type: string

CatBehavior:
type: object
properties:
mainTrait:
type: string
enum:
- cuddly
- aggressive
- lazy

dangerous:
type: boolean


Cat:
description: A representation of a cat
allOf:
- $ref: '#/definitions/Pet'
- type: object
properties:
behaviors:
type: array
default: []
description: A list of behaviors for this Cat
items:
$ref: "#/definitions/CatBehavior"

huntingSkill:
type: string
description: The measured skill for hunting
Expand All @@ -668,6 +760,7 @@ definitions:
- lazy
- adventurous
- aggressive

required:
- huntingSkill
Category:
Expand All @@ -690,12 +783,32 @@ definitions:
description: Dumb Property
xml:
name: Category

DogBehavior:
type: object
properties:
mainTrait:
type: string
enum:
- noisy
- slimy
- dependent

dangerous:
type: boolean

Dog:
description: A representation of a dog
allOf:
- $ref: '#/definitions/Pet'
- type: object
properties:
behaviors:
type: array
default: []
description: A list of behaviors for this Dog
items:
$ref: "#/definitions/DogBehavior"
packSize:
type: integer
format: int32
Expand Down
11 changes: 9 additions & 2 deletions lib/components/JsonSchema/json-schema.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,17 @@
[nestOdd]="!nestOdd" [isRequestSchema]="isRequestSchema"> </json-schema>
</ng-template>
<ng-template ngSwitchCase="object">
<div class="one-of-selector" *ngIf="schema._wrapped.length > 1">
{{schema._choiceTitle}}
<drop-down (change)="selectChoice($event)" [active]="activeChoice">
<option *ngFor="let item of schema._wrapped; let i=index"
[value]="i" [attr.selected]="activeChoice === i ? '' : null" >{{item.title}}</option>
</drop-down>
</div>

<table class="params-wrap" [ngClass]="{'params-array': _isArray}">
<!-- <caption> {{_displayType}} </caption> -->
<ng-template ngFor [ngForOf]="properties" let-prop="$implicit" let-last="last" [ngForTrackBy]="trackByName">
<ng-template ngFor [ngForOf]="schema._wrapped[activeChoice].displayProperties" let-prop="$implicit" let-last="last" [ngForTrackBy]="trackByName">
<tr class="param" [ngClass]="{'last': last,
'discriminator': prop.isDiscriminator,
'complex': prop._pointer,
Expand Down Expand Up @@ -103,5 +111,4 @@
</ng-template>
</table>
</ng-template>

</ng-container>
4 changes: 4 additions & 0 deletions lib/components/JsonSchema/json-schema.scss
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ table {
}
}

.one-of-selector {
text-align: right;
}

ul, li {
margin: 0;
}
Expand Down
51 changes: 35 additions & 16 deletions lib/components/JsonSchema/json-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class JsonSchema extends BaseSearchableComponent implements OnInit {
_isArray: boolean;
normalizer: SchemaNormalizer;
descendants: DescendantInfo[];
activeChoice: number;

constructor(
specMgr: SpecManager,
Expand Down Expand Up @@ -96,6 +97,11 @@ export class JsonSchema extends BaseSearchableComponent implements OnInit {
this.selectDescendantByIdx(0);
}

selectChoice(index) {
if (typeof index === "object") return;
this.activeChoice = index
}

init() {
if (!this.pointer) return;
if (!this.absolutePointer) this.absolutePointer = this.pointer;
Expand All @@ -112,38 +118,51 @@ export class JsonSchema extends BaseSearchableComponent implements OnInit {
this._isArray = this.schema._isArray;
this.absolutePointer += (this._isArray ? '/items' : '');
this.initDescendants();
this.selectChoice(0);
this.preprocessSchema();
}

preprocessSchema() {
SchemaHelper.preprocess(this.schema, this.normPointer, this.pointer);

if (!this.schema.isTrivial) {
if (!this.schema.isTrivial && !this.schema._wrapped) {
SchemaHelper.preprocessProperties(this.schema, this.normPointer, {
childFor: this.childFor,
discriminator: this.discriminator
});
}

this.properties = this.schema._properties || [];
if (this.isRequestSchema) {
this.properties = this.properties.filter(prop => !prop.readOnly);
}
if (this.schema._wrapped) {
this.schema._wrapped.forEach((schema) => {
SchemaHelper.preprocessProperties(schema, this.normPointer, {
childFor: this.childFor,
discriminator: this.discriminator
});

if (this.optionsService.options.requiredPropsFirst) {
SchemaHelper.moveRequiredPropsFirst(this.properties, this.schema.required);
}
let properties = schema._properties || [];

this._hasSubSchemas = this.properties && this.properties.some(
propSchema => {
if (propSchema.type === 'array') {
propSchema = propSchema.items;
if (this.isRequestSchema) {
properties = properties.filter(prop => !prop.readOnly);
}

if (this.optionsService.options.requiredPropsFirst) {
SchemaHelper.moveRequiredPropsFirst(properties, schema.required);
}

this._hasSubSchemas = properties && properties.some(
propSchema => {
if (propSchema.type === 'array') {
propSchema = propSchema.items;
}
return (propSchema && propSchema.type === 'object' && propSchema._pointer);
});

if (properties.length === 1) {
properties[0].expanded = true;
}
return (propSchema && propSchema.type === 'object' && propSchema._pointer);
});

if (this.properties.length === 1) {
this.properties[0].expanded = true;
schema.displayProperties = properties
})
}
}

Expand Down
26 changes: 21 additions & 5 deletions lib/services/schema-helper.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,33 @@ const injectors = {
},
object: {
check: (propertySchema) => {
return propertySchema.type === 'object' && (propertySchema.properties ||
let isObject = propertySchema.type === 'object' && (propertySchema.properties ||
typeof propertySchema.additionalProperties === 'object');

let isChoiceObject = propertySchema.oneOf && propertySchema.oneOf.length > 0 ||
propertySchema.anyOf && propertySchema.anyOf.length > 0

return isObject || isChoiceObject;
},
inject: (injectTo, propertySchema = injectTo) => {
let baseName = propertySchema._pointer && JsonPointer.baseName(propertySchema._pointer);
injectTo._displayType = propertySchema.title || baseName || 'object';
injectTo._widgetType = 'object';
if (propertySchema.type === 'object') {
let baseName = propertySchema._pointer && JsonPointer.baseName(propertySchema._pointer);
injectTo._displayType = propertySchema.title || baseName || 'object';
injectTo._wrapped = [propertySchema];
} else if (propertySchema.oneOf) {
injectTo._displayType = 'oneOf';
injectTo._choiceTitle = 'One of';
injectTo._wrapped = propertySchema.oneOf
} else if (propertySchema.anyOf) {
injectTo._displayType = 'anyOf';
injectTo._choiceTitle = 'Any of';
injectTo._wrapped = propertySchema.anyOf
}
}
},
noType: {
check: (propertySchema) => !propertySchema.type,
check: (propertySchema) => !propertySchema.type && !propertySchema.oneOf && !propertySchema.anyOf,
inject: (injectTo) => {
injectTo._displayType = '< anything >';
injectTo._displayTypeHint = 'This field may contain data of any type';
Expand All @@ -110,7 +126,7 @@ const injectors = {
return (!propertySchema.properties || !Object.keys(propertySchema.properties).length)
&& (typeof propertySchema.additionalProperties !== 'object');
}
return (propertySchema.type !== 'array') && propertySchema.type;
return (propertySchema.type !== 'array' && !propertySchema.oneOf && !propertySchema.anyOf) && propertySchema.type;
},
inject: (injectTo, propertySchema = injectTo) => {
injectTo.isTrivial = true;
Expand Down
12 changes: 12 additions & 0 deletions lib/services/schema-normalizer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ interface Reference {
interface Schema {
properties: any;
allOf: any;
oneOf: any;
anyOf: any;
items: any;
additionalProperties: any;
}
Expand Down Expand Up @@ -73,6 +75,16 @@ class SchemaWalker {
SchemaWalker.walkEach(obj.allOf, ptr, visitor);
}

if (obj.oneOf) {
let ptr = JsonPointer.join(pointer, ['oneOf']);
SchemaWalker.walkEach(obj.oneOf, ptr, visitor);
}

if (obj.anyOf) {
let ptr = JsonPointer.join(pointer, ['anyOf']);
SchemaWalker.walkEach(obj.anyOf, ptr, visitor);
}

if (obj.items) {
let ptr = JsonPointer.join(pointer, ['items']);
if (Array.isArray(obj.items)) {
Expand Down