diff --git a/defaultSort.json b/defaultSort.json index fec73a5..b690fd3 100644 --- a/defaultSort.json +++ b/defaultSort.json @@ -5,12 +5,12 @@ "put": ["operationId", "summary", "description", "parameters", "requestBody", "responses"], "patch": ["operationId", "summary", "description", "parameters", "requestBody", "responses"], "delete": ["operationId", "summary", "description", "parameters", "requestBody", "responses"], - "parameters": ["name", "in", "description", "required", "schema"], + "parameters[*]": ["name", "in", "description", "required", "schema"], "requestBody": ["description", "required", "content"], - "responses": ["description", "headers", "content", "links"], + "responses[*]": ["description", "headers", "content", "links"], "content": [], "components": ["parameters", "schemas"], "schema": ["description", "type", "items", "properties", "format", "example", "default"], - "schemas": ["description", "type", "items", "properties", "format", "example", "default"], - "properties": ["description", "type", "items", "format", "example", "default", "enum"] + "schemas[*]": ["description", "type", "items", "properties", "format", "example", "default"], + "properties[*]": ["description", "type", "items", "format", "example", "default", "enum"] } diff --git a/openapi-format.js b/openapi-format.js index d07c174..a40985a 100644 --- a/openapi-format.js +++ b/openapi-format.js @@ -48,53 +48,42 @@ async function openapiSort(oaObj, options) { // Recursive traverse through OpenAPI document traverse(jsonObj).forEach(function (node) { - if (typeof node === 'object') { - - // Components sorting by alphabet - if (this.parent && this.parent.key && this.path[0] === 'components' && this.parent.key === 'components' - && sortComponentsSet.length > 0 && sortComponentsSet.includes(this.key) - ) { - // debugStep = 'Component sorting by alphabet' - let sortedObj = JSON.parse(JSON.stringify(node)); // Deep copy of the schema object - node = prioritySort(sortedObj, []); - this.update(node); - } + if (typeof node !== 'object') { + return; + } - // Generic sorting - if (sortSet.hasOwnProperty(this.key) && Array.isArray(sortSet[this.key])) { + // Components sorting by alphabet + if (this.parent && this.parent.key && this.path[0] === 'components' && this.parent.key === 'components' + && sortComponentsSet.length > 0 && sortComponentsSet.includes(this.key) + ) { + // debugStep = 'Component sorting by alphabet' + let sortedObj = JSON.parse(JSON.stringify(node)); // Deep copy of the schema object + node = prioritySort(sortedObj, []); + this.update(node); + } - if (Array.isArray(node)) { - // debugStep = 'Generic sorting - array' - // Deep sort array of properties - let sortedObj = JSON.parse(JSON.stringify(node)); // Deep copy of the schema object - for (let i = 0; i < sortedObj.length; i++) { - sortedObj[i] = prioritySort(sortedObj[i], sortSet[this.key]); - } - this.update(sortedObj); - - } else if ((this.key === 'responses' || this.key === 'schemas' || this.key === 'properties') - && (this.parent && this.parent.key !== 'properties' && this.parent.key !== 'value' && this.path[1] !== 'examples') - ) { - // debugStep = 'Generic sorting - responses/schemas/properties' - // Deep sort list of properties - let sortedObj = JSON.parse(JSON.stringify(node)); // Deep copy of the object - for (let keyRes in sortedObj) { - sortedObj[keyRes] = prioritySort(sortedObj[keyRes], sortSet[this.key]); - } - this.update(sortedObj); - } else { - if (this.path[0] === 'components' && this.path[1] === 'examples' && this.path[3] === 'value') { - // debugStep = 'Generic sorting - skip nested components>examples' - // Skip nested components>examples values - } else { - // debugStep = 'Generic sorting - properties' - // Sort list of properties - this.update(prioritySort(node, sortSet[this.key])); - } + // Generic sorting + if (sortSet.hasOwnProperty(this.key) && Array.isArray(sortSet[this.key])) { - } + if (this.path[0] === 'components' && this.path[1] === 'examples' && this.path[3] === 'value') { + // debugStep = 'Generic sorting - skip nested components>examples' + // Skip nested components>examples values + } else { + // debugStep = 'Generic sorting - properties' + // Sort list of properties + this.update(prioritySort(node, sortSet[this.key])); } } + + if (!this.parent || (this.path[0] === 'components' && this.path[1] === 'examples' && this.path[3] === 'value')) { + return; + } + + // Nested sorting + let globKey = this.parent.key + "[*]"; + if (sortSet.hasOwnProperty(globKey) && Array.isArray(sortSet[globKey])) { + this.update(prioritySort(node, sortSet[globKey])); + } }); // Process root level diff --git a/package-lock.json b/package-lock.json index 157e6db..5774403 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "openapi-format": "bin/cli.js" }, "devDependencies": { - "jest": "^27.4.7" + "jest": "^27.5.1" }, "engines": { "node": ">=12" diff --git a/test/__utils__/test-utils.js b/test/__utils__/test-utils.js index db2b033..a5e0d32 100644 --- a/test/__utils__/test-utils.js +++ b/test/__utils__/test-utils.js @@ -3,8 +3,8 @@ const sy = require("@stoplight/yaml"); const {exec} = require("child_process"); const path = require("path"); -async function loadTest(folder, inputType = 'yaml', outType = 'yaml') { - let input, outputBefore, outputAfter = {} +async function loadRawTest(folder, inputType = 'yaml', outType = 'yaml') { + let input = outputBefore = outputAfter = '{}' const testPath = `./test/${folder}` const inputFile = `input.${inputType}` const inputPath = `./test/${folder}/${inputFile}` @@ -12,21 +12,13 @@ async function loadTest(folder, inputType = 'yaml', outType = 'yaml') { const outputPath = `./test/${folder}/${outputFile}` try { - if (outType === 'json') { - input = JSON.parse(fs.readFileSync(inputPath, 'utf8')); - } else { - input = sy.parse(fs.readFileSync(inputPath, 'utf8')); - } + input = fs.readFileSync(inputPath, 'utf8'); } catch (err) { // File not found = {} will be used } try { - if (outType === 'json') { - outputBefore = JSON.parse(fs.readFileSync(outputPath, 'utf8')); - } else { - outputBefore = sy.parse(fs.readFileSync(outputPath, 'utf8')); - } + outputBefore = fs.readFileSync(outputPath, 'utf8'); } catch (err) { // File not found = {} will be used } @@ -34,11 +26,7 @@ async function loadTest(folder, inputType = 'yaml', outType = 'yaml') { let result = await cli([`${inputFile}`, `--configFile options.yaml`], testPath); try { - if (outType === 'json') { - outputAfter = JSON.parse(fs.readFileSync(outputPath, 'utf8')); - } else { - outputAfter = sy.parse(fs.readFileSync(outputPath, 'utf8')); - } + outputAfter = fs.readFileSync(outputPath, 'utf8'); } catch (err) { // } @@ -46,6 +34,16 @@ async function loadTest(folder, inputType = 'yaml', outType = 'yaml') { return {result: result, input: input, outputBefore: outputBefore, outputAfter: outputAfter} } +async function loadTest(folder, inputType = 'yaml', outType = 'yaml') { + let raw = await loadRawTest(folder, inputType, outType) + + raw.input = (inputType === 'json') ? JSON.parse(raw.input) : sy.parse(raw.input); + raw.outputBefore = (outType === 'json') ? JSON.parse(raw.outputBefore) : sy.parse(raw.outputBefore); + raw.outputAfter = (outType === 'json') ? JSON.parse(raw.outputAfter) : sy.parse(raw.outputAfter); + + return raw +} + function cli(args, cwd) { return new Promise(resolve => { exec( @@ -64,6 +62,7 @@ function cli(args, cwd) { } module.exports = { + loadRawTest: loadRawTest, loadTest: loadTest, cli: cli }; diff --git a/test/json-custom-yaml/customSort.json b/test/json-custom-yaml/customSort.json index 8224a3e..5fed851 100644 --- a/test/json-custom-yaml/customSort.json +++ b/test/json-custom-yaml/customSort.json @@ -5,12 +5,12 @@ "put": ["operationId", "summary", "description", "parameters", "requestBody", "responses"], "patch": ["operationId", "summary", "description", "parameters", "requestBody", "responses"], "delete": ["operationId", "summary", "description", "parameters", "requestBody", "responses"], - "parameters": ["required", "schema", "name", "in", "description"], + "parameters[*]": ["required", "schema", "name", "in", "description"], "requestBody": ["description", "headers", "content", "links"], - "responses": ["description", "headers", "content", "links"], + "responses[*]": ["description", "headers", "content", "links"], "content": [], "components": ["parameters", "schemas"], - "Schema": ["description", "type", "items", "properties", "format", "example", "default"], - "schemas": ["description", "type", "items", "properties", "format", "example", "default"], - "properties": ["description", "type", "items", "format", "example", "default", "enum"] + "schema": ["description", "type", "items", "properties", "format", "example", "default"], + "schemas[*]": ["description", "type", "items", "properties", "format", "example", "default"], + "properties[*]": ["description", "type", "items", "format", "example", "default", "enum"] } diff --git a/test/json-custom/customSort.json b/test/json-custom/customSort.json index 8224a3e..5fed851 100644 --- a/test/json-custom/customSort.json +++ b/test/json-custom/customSort.json @@ -5,12 +5,12 @@ "put": ["operationId", "summary", "description", "parameters", "requestBody", "responses"], "patch": ["operationId", "summary", "description", "parameters", "requestBody", "responses"], "delete": ["operationId", "summary", "description", "parameters", "requestBody", "responses"], - "parameters": ["required", "schema", "name", "in", "description"], + "parameters[*]": ["required", "schema", "name", "in", "description"], "requestBody": ["description", "headers", "content", "links"], - "responses": ["description", "headers", "content", "links"], + "responses[*]": ["description", "headers", "content", "links"], "content": [], "components": ["parameters", "schemas"], - "Schema": ["description", "type", "items", "properties", "format", "example", "default"], - "schemas": ["description", "type", "items", "properties", "format", "example", "default"], - "properties": ["description", "type", "items", "format", "example", "default", "enum"] + "schema": ["description", "type", "items", "properties", "format", "example", "default"], + "schemas[*]": ["description", "type", "items", "properties", "format", "example", "default"], + "properties[*]": ["description", "type", "items", "format", "example", "default", "enum"] } diff --git a/test/json-sort-properties/customSort.json b/test/json-sort-properties/customSort.json new file mode 100644 index 0000000..e732365 --- /dev/null +++ b/test/json-sort-properties/customSort.json @@ -0,0 +1,18 @@ +{ + "root": ["openapi", "info", "servers", "paths", "components", "tags", "x-tagGroups", "externalDocs"], + "get": ["operationId", "summary", "description", "parameters", "requestBody", "responses"], + "post": ["operationId", "summary", "description", "parameters", "requestBody", "responses"], + "put": ["operationId", "summary", "description", "parameters", "requestBody", "responses"], + "patch": ["operationId", "summary", "description", "parameters", "requestBody", "responses"], + "delete": ["operationId", "summary", "description", "parameters", "requestBody", "responses"], + "parameters": ["name"], + "parameters[*]": ["name", "in", "description", "required", "schema"], + "requestBody": ["description", "required", "content"], + "responses[*]": ["description", "headers", "content", "links"], + "content": [], + "components": ["parameters", "schemas"], + "schema": ["description", "type", "items", "properties", "format", "example", "default"], + "schemas[*]": ["description", "type", "items", "properties", "format", "example", "default"], + "properties[*]": ["description", "type", "items", "format", "example", "default", "enum"], + "properties": [] +} diff --git a/test/json-sort-properties/input.json b/test/json-sort-properties/input.json new file mode 100644 index 0000000..9ab50d3 --- /dev/null +++ b/test/json-sort-properties/input.json @@ -0,0 +1,530 @@ +{ + "openapi": "3.0.2", + "servers": [ + { + "url": "/v3" + } + ], + "info": { + "description": "This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about\nSwagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\nYou can now help us improve the API whether it's by making changes to the definition itself or to the code.\nThat way, with time, we can improve the API in general, and expose some of the new features in OAS3.\n\nSome useful links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)", + "version": "1.0.6-SNAPSHOT", + "title": "Swagger Petstore - OpenAPI 3.0", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + } + ], + "paths": { + "/pet": { + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "Add a new pet to the store", + "operationId": "addPet", + "responses": { + "200": { + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "description": "Successful operation" + }, + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "requestBody": { + "description": "Create a new pet in the store", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "Update an existing pet by Id", + "operationId": "updatePet", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "requestBody": { + "description": "Update an existent pet in the store", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "operationId": "findPetsByStatus", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": false, + "explode": true, + "schema": { + "type": "string", + "default": "available", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + { + "name": "limit", + "in": "query", + "description": "Maximal number of pets to return", + "required": false, + "schema": { + "type": "integer", + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + } + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + }, + "components": { + "schemas": { + "Order": { + "x-swagger-router-model": "io.swagger.petstore.model.Order", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "petId": { + "type": "integer", + "format": "int64", + "example": 198772 + }, + "quantity": { + "type": "integer", + "format": "int32", + "example": 7 + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "enum": [ + "placed", + "approved", + "delivered" + ], + "example": "approved" + }, + "complete": { + "type": "boolean" + } + }, + "xml": { + "name": "order" + }, + "type": "object" + }, + "Customer": { + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 100000 + }, + "username": { + "type": "string", + "example": "fehguy" + }, + "address": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Address" + }, + "xml": { + "wrapped": true, + "name": "addresses" + } + } + }, + "xml": { + "name": "customer" + }, + "type": "object" + }, + "Address": { + "properties": { + "street": { + "type": "string", + "example": "437 Lytton" + }, + "city": { + "type": "string", + "example": "Palo Alto" + }, + "state": { + "type": "string", + "example": "CA" + }, + "zip": { + "type": "string", + "example": 94301 + } + }, + "xml": { + "name": "address" + }, + "type": "object" + }, + "Category": { + "x-swagger-router-model": "io.swagger.petstore.model.Category", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "type": "string", + "example": "Dogs" + } + }, + "xml": { + "name": "category" + }, + "type": "object" + }, + "User": { + "x-swagger-router-model": "io.swagger.petstore.model.User", + "properties": { + "id": { + "example": 10, + "format": "int64", + "type": "integer" + }, + "username": { + "type": "string", + "example": "theUser" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "James" + }, + "email": { + "type": "string", + "example": "john@email.com" + }, + "password": { + "type": "string", + "example": 12345 + }, + "phone": { + "type": "string", + "example": 12345 + }, + "userStatus": { + "type": "integer", + "format": "int32", + "example": 1, + "description": "User Status" + } + }, + "xml": { + "name": "user" + }, + "type": "object" + }, + "Tag": { + "x-swagger-router-model": "io.swagger.petstore.model.Tag", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "tag" + }, + "type": "object" + }, + "Pet": { + "x-swagger-router-model": "io.swagger.petstore.model.Pet", + "required": [ + "name", + "photoUrls" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "name": { + "type": "string", + "example": "doggie" + }, + "category": { + "$ref": "#/components/schemas/Category" + }, + "photoUrls": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + } + }, + "tags": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "$ref": "#/components/schemas/Tag", + "xml": { + "name": "tag" + } + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "pet" + }, + "type": "object" + }, + "ApiResponse": { + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "xml": { + "name": "##default" + }, + "type": "object" + } + }, + "requestBodies": { + "Pet": { + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "description": "Pet object that needs to be added to the store" + }, + "UserArray": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "description": "List of user object" + } + }, + "securitySchemes": { + "petstore_auth": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://petstore.swagger.io/oauth/authorize", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + } +} diff --git a/test/json-sort-properties/options.yaml b/test/json-sort-properties/options.yaml new file mode 100644 index 0000000..1f29cc0 --- /dev/null +++ b/test/json-sort-properties/options.yaml @@ -0,0 +1,3 @@ +verbose: true +output: output.json +sortFile: customSort.json diff --git a/test/json-sort-properties/output.json b/test/json-sort-properties/output.json new file mode 100644 index 0000000..5e08a41 --- /dev/null +++ b/test/json-sort-properties/output.json @@ -0,0 +1,530 @@ +{ + "openapi": "3.0.2", + "info": { + "description": "This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about\nSwagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\nYou can now help us improve the API whether it's by making changes to the definition itself or to the code.\nThat way, with time, we can improve the API in general, and expose some of the new features in OAS3.\n\nSome useful links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)", + "version": "1.0.6-SNAPSHOT", + "title": "Swagger Petstore - OpenAPI 3.0", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "servers": [ + { + "url": "/v3" + } + ], + "paths": { + "/pet": { + "post": { + "operationId": "addPet", + "summary": "Add a new pet to the store", + "description": "Add a new pet to the store", + "requestBody": { + "description": "Create a new pet in the store", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "tags": [ + "pet" + ] + }, + "put": { + "operationId": "updatePet", + "summary": "Update an existing pet", + "description": "Update an existing pet by Id", + "requestBody": { + "description": "Update an existent pet in the store", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "tags": [ + "pet" + ] + } + }, + "/pet/findByStatus": { + "get": { + "operationId": "findPetsByStatus", + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "Maximal number of pets to return", + "required": false, + "schema": { + "type": "integer", + "default": 100 + } + }, + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": false, + "schema": { + "type": "string", + "default": "available", + "enum": [ + "available", + "pending", + "sold" + ] + }, + "explode": true + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "tags": [ + "pet" + ] + } + } + }, + "components": { + "schemas": { + "Order": { + "type": "object", + "properties": { + "complete": { + "type": "boolean" + }, + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "petId": { + "type": "integer", + "format": "int64", + "example": 198772 + }, + "quantity": { + "type": "integer", + "format": "int32", + "example": 7 + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "description": "Order Status", + "type": "string", + "example": "approved", + "enum": [ + "placed", + "approved", + "delivered" + ] + } + }, + "x-swagger-router-model": "io.swagger.petstore.model.Order", + "xml": { + "name": "order" + } + }, + "Customer": { + "type": "object", + "properties": { + "address": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Address" + }, + "xml": { + "wrapped": true, + "name": "addresses" + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 100000 + }, + "username": { + "type": "string", + "example": "fehguy" + } + }, + "xml": { + "name": "customer" + } + }, + "Address": { + "type": "object", + "properties": { + "city": { + "type": "string", + "example": "Palo Alto" + }, + "state": { + "type": "string", + "example": "CA" + }, + "street": { + "type": "string", + "example": "437 Lytton" + }, + "zip": { + "type": "string", + "example": 94301 + } + }, + "xml": { + "name": "address" + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "type": "string", + "example": "Dogs" + } + }, + "x-swagger-router-model": "io.swagger.petstore.model.Category", + "xml": { + "name": "category" + } + }, + "User": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "john@email.com" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "lastName": { + "type": "string", + "example": "James" + }, + "password": { + "type": "string", + "example": 12345 + }, + "phone": { + "type": "string", + "example": 12345 + }, + "userStatus": { + "description": "User Status", + "type": "integer", + "format": "int32", + "example": 1 + }, + "username": { + "type": "string", + "example": "theUser" + } + }, + "x-swagger-router-model": "io.swagger.petstore.model.User", + "xml": { + "name": "user" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "x-swagger-router-model": "io.swagger.petstore.model.Tag", + "xml": { + "name": "tag" + } + }, + "Pet": { + "type": "object", + "properties": { + "category": { + "$ref": "#/components/schemas/Category" + }, + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + }, + "xml": { + "wrapped": true + } + }, + "status": { + "description": "pet status in the store", + "type": "string", + "enum": [ + "available", + "pending", + "sold" + ] + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Tag", + "xml": { + "name": "tag" + } + }, + "xml": { + "wrapped": true + } + } + }, + "required": [ + "name", + "photoUrls" + ], + "x-swagger-router-model": "io.swagger.petstore.model.Pet", + "xml": { + "name": "pet" + } + }, + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "xml": { + "name": "##default" + } + } + }, + "requestBodies": { + "Pet": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "description": "Pet object that needs to be added to the store" + }, + "UserArray": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "description": "List of user object" + } + }, + "securitySchemes": { + "petstore_auth": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://petstore.swagger.io/oauth/authorize", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + }, + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + } + ], + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + } +} \ No newline at end of file diff --git a/test/sorting.test.js b/test/sorting.test.js index a16e587..272fc59 100644 --- a/test/sorting.test.js +++ b/test/sorting.test.js @@ -9,7 +9,7 @@ describe('openapi-format CLI sorting tests', () => { describe('json-custom', () => { it('json-custom - should match expected output', async () => { const testName = 'json-custom' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName, 'json', 'json') + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName, 'json', 'json') // console.log('result',result) expect(outputAfter).toStrictEqual(outputBefore); expect(result.code).toBe(0); @@ -20,7 +20,7 @@ describe('openapi-format CLI sorting tests', () => { describe('json-custom-yaml', () => { it('json-custom-yaml - should match expected output', async () => { const testName = 'json-custom-yaml' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName, 'json', 'yaml') + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName, 'json', 'yaml') // console.log('result',result) expect(outputAfter).toStrictEqual(outputBefore); expect(result.code).toBe(0); @@ -31,7 +31,7 @@ describe('openapi-format CLI sorting tests', () => { describe('json-default', () => { it('json-default - should match expected output', async () => { const testName = 'json-default' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName, 'json', 'json') + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName, 'json', 'json') // console.log('result',result) expect(result.code).toBe(0); expect(result.stdout).toContain("formatted successfully"); @@ -42,7 +42,7 @@ describe('openapi-format CLI sorting tests', () => { describe('json-default-yaml', () => { it('json-default-yaml - should match expected output', async () => { const testName = 'json-default-yaml' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName, 'json', 'yaml') + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName, 'json', 'yaml') // console.log('result',result) expect(result.code).toBe(0); expect(result.stdout).toContain("formatted successfully"); @@ -50,10 +50,21 @@ describe('openapi-format CLI sorting tests', () => { }); }) + describe('json-sort-properties', () => { + it('json-sort-properties - should match expected output', async () => { + const testName = 'json-sort-properties' + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName, 'json', 'json') + // console.log('result',result) + expect(outputAfter).toStrictEqual(outputBefore); + expect(result.code).toBe(0); + expect(result.stdout).toContain("formatted successfully"); + }); + }) + describe('json-no-sort', () => { it('json-no-sort - should match expected output', async () => { const testName = 'json-no-sort' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName, 'json', 'json') + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName, 'json', 'json') // console.log('result',result) expect(result.code).toBe(0); expect(result.stdout).toContain("formatted successfully"); @@ -64,7 +75,7 @@ describe('openapi-format CLI sorting tests', () => { describe('json-rename', () => { it('json-rename - should match expected output', async () => { const testName = 'json-rename' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName, 'json', 'json') + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName, 'json', 'json') // console.log('result',result) expect(result.code).toBe(0); expect(result.stdout).toContain("formatted successfully"); @@ -75,7 +86,7 @@ describe('openapi-format CLI sorting tests', () => { describe('json-sort-components', () => { it('json-sort-components - should match expected output', async () => { const testName = 'json-sort-components' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName, 'json', 'json') + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName, 'json', 'json') // console.log('result',result) expect(result.code).toBe(0); expect(result.stdout).toContain("formatted successfully"); @@ -86,7 +97,7 @@ describe('openapi-format CLI sorting tests', () => { describe('yaml-custom', () => { it('yaml-custom - should match expected output', async () => { const testName = 'yaml-custom' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName) + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName) // console.log('result',result) expect(result.code).toBe(0); expect(result.stdout).toContain("formatted successfully"); @@ -97,7 +108,7 @@ describe('openapi-format CLI sorting tests', () => { describe('yaml-custom-json', () => { it('yaml-custom-json - should match expected output', async () => { const testName = 'yaml-custom-json' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName, 'yaml', 'json') + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName, 'yaml', 'json') // console.log('result',result) expect(result.code).toBe(0); expect(result.stdout).toContain("formatted successfully"); @@ -108,7 +119,7 @@ describe('openapi-format CLI sorting tests', () => { describe('yaml-default', () => { it('yaml-default - should match expected output', async () => { const testName = 'yaml-default' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName) + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName) // console.log('result',result) expect(result.code).toBe(0); expect(result.stdout).toContain("formatted successfully"); @@ -119,7 +130,7 @@ describe('openapi-format CLI sorting tests', () => { describe('yaml-default-bug-examples-properties', () => { it('yaml-default-bug-examples-properties - should match expected output', async () => { const testName = 'yaml-default-bug-examples-properties' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName) + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName) // console.log('result',result) expect(result.code).toBe(0); expect(result.stdout).toContain("formatted successfully"); @@ -130,7 +141,7 @@ describe('openapi-format CLI sorting tests', () => { describe('yaml-default-bug-nested-properties', () => { it('yaml-default-bug-nested-properties - should match expected output', async () => { const testName = 'yaml-default-bug-nested-properties' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName) + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName) // console.log('result',result) expect(result.code).toBe(0); expect(result.stdout).toContain("formatted successfully"); @@ -141,7 +152,7 @@ describe('openapi-format CLI sorting tests', () => { describe('yaml-default-json', () => { it('yaml-default-json - should match expected output', async () => { const testName = 'yaml-default-json' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName, 'yaml', 'json') + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName, 'yaml', 'json') // console.log('result',result) expect(result.code).toBe(0); expect(result.stdout).toContain("formatted successfully"); @@ -152,7 +163,7 @@ describe('openapi-format CLI sorting tests', () => { describe('yaml-linewidth', () => { it('yaml-linewidth - should match expected output', async () => { const testName = 'yaml-linewidth' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName) + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName) // console.log('result',result) expect(result.code).toBe(0); expect(result.stdout).toContain("formatted successfully"); @@ -163,7 +174,7 @@ describe('openapi-format CLI sorting tests', () => { describe('yaml-no-sort', () => { it('yaml-no-sort - should match expected output', async () => { const testName = 'yaml-no-sort' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName) + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName) // console.log('result',result) expect(result.code).toBe(0); expect(result.stdout).toContain("formatted successfully"); @@ -174,7 +185,7 @@ describe('openapi-format CLI sorting tests', () => { describe('yaml-rename', () => { it('yaml-rename - should match expected output', async () => { const testName = 'yaml-rename' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName) + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName) // console.log('result',result) expect(result.code).toBe(0); expect(result.stdout).toContain("formatted successfully"); @@ -185,7 +196,7 @@ describe('openapi-format CLI sorting tests', () => { describe('yaml-sort-components', () => { it('yaml-sort-components - should match expected output', async () => { const testName = 'yaml-sort-components' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName) + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName) // console.log('result',result) expect(result.code).toBe(0); expect(result.stdout).toContain("formatted successfully"); @@ -196,7 +207,7 @@ describe('openapi-format CLI sorting tests', () => { describe('yaml-stoplight-studio-style', () => { it('yaml-stoplight-studio-style - should match expected output', async () => { const testName = 'yaml-stoplight-studio-style' - const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName) + const {result, input, outputBefore, outputAfter} = await testUtils.loadRawTest(testName) // console.log('result',result) expect(result.code).toBe(0); expect(result.stdout).toContain("formatted successfully"); diff --git a/test/yaml-custom-3.1/customSort.yaml b/test/yaml-custom-3.1/customSort.yaml index f342b45..c03e3f1 100644 --- a/test/yaml-custom-3.1/customSort.yaml +++ b/test/yaml-custom-3.1/customSort.yaml @@ -42,7 +42,7 @@ delete: - parameters - requestBody - responses -parameters: +parameters[*]: - required - schema - name @@ -53,7 +53,7 @@ requestBody: - headers - content - links -responses: +responses[*]: - description - headers - content @@ -62,7 +62,7 @@ content: [] components: - parameters - schemas -Schema: +schema: - description - type - items @@ -70,7 +70,7 @@ Schema: - format - example - default -schemas: +schemas[*]: - description - type - items @@ -78,7 +78,7 @@ schemas: - format - example - default -properties: +properties[*]: - description - type - items diff --git a/test/yaml-custom-json/customSort.yaml b/test/yaml-custom-json/customSort.yaml index f342b45..c03e3f1 100644 --- a/test/yaml-custom-json/customSort.yaml +++ b/test/yaml-custom-json/customSort.yaml @@ -42,7 +42,7 @@ delete: - parameters - requestBody - responses -parameters: +parameters[*]: - required - schema - name @@ -53,7 +53,7 @@ requestBody: - headers - content - links -responses: +responses[*]: - description - headers - content @@ -62,7 +62,7 @@ content: [] components: - parameters - schemas -Schema: +schema: - description - type - items @@ -70,7 +70,7 @@ Schema: - format - example - default -schemas: +schemas[*]: - description - type - items @@ -78,7 +78,7 @@ schemas: - format - example - default -properties: +properties[*]: - description - type - items diff --git a/test/yaml-custom-json/output.json b/test/yaml-custom-json/output.json index 51d3992..2a45af0 100644 --- a/test/yaml-custom-json/output.json +++ b/test/yaml-custom-json/output.json @@ -148,12 +148,12 @@ "required": false, "schema": { "type": "string", + "default": "available", "enum": [ "available", "pending", "sold" - ], - "default": "available" + ] }, "name": "status", "in": "query", diff --git a/test/yaml-custom/customSort.yaml b/test/yaml-custom/customSort.yaml index f342b45..c03e3f1 100644 --- a/test/yaml-custom/customSort.yaml +++ b/test/yaml-custom/customSort.yaml @@ -42,7 +42,7 @@ delete: - parameters - requestBody - responses -parameters: +parameters[*]: - required - schema - name @@ -53,7 +53,7 @@ requestBody: - headers - content - links -responses: +responses[*]: - description - headers - content @@ -62,7 +62,7 @@ content: [] components: - parameters - schemas -Schema: +schema: - description - type - items @@ -70,7 +70,7 @@ Schema: - format - example - default -schemas: +schemas[*]: - description - type - items @@ -78,7 +78,7 @@ schemas: - format - example - default -properties: +properties[*]: - description - type - items diff --git a/test/yaml-custom/output.yaml b/test/yaml-custom/output.yaml index db32af5..91aff45 100644 --- a/test/yaml-custom/output.yaml +++ b/test/yaml-custom/output.yaml @@ -101,11 +101,11 @@ paths: - required: false schema: type: string + default: available enum: - available - pending - sold - default: available name: status in: query description: Status values that need to be considered for filter diff --git a/util-sort.js b/util-sort.js index 8fd523b..37d5cde 100644 --- a/util-sort.js +++ b/util-sort.js @@ -29,13 +29,13 @@ function sortObjectByKeyNameList(object, sortWith) { * @returns {(function(*=, *=): (number|number))|*} */ function propComparator(priorityArr) { + if (!Array.isArray(priorityArr)) { + return function (a, b) { return 0 } + } return function (a, b) { if (a === b) { return 0; } - if (!Array.isArray(priorityArr)) { - return 0; - } const ia = priorityArr.indexOf(a); const ib = priorityArr.indexOf(b); if (ia !== -1) { @@ -45,15 +45,64 @@ function propComparator(priorityArr) { } } +/** + * Sort array by property value of items + * @param array + * @param sortWith + * @rturns {*} + */ +function sortArrayByItemProps(array, sortWith) { + const sortedArray = Array.from(array); + sortedArray.sort(sortWith); + return sortedArray; +} + +/** + * Compare objects by their property values + * @param priorityArr + * @returns {(function(*=, *=): (number|number))|*} + */ +function objComparator(priorityArr) { + if (!Array.isArray(priorityArr)) { + return function (a, b) { return 0 } + } + return function (a, b) { + if (a === b) { + return 0; + } + for (const key of priorityArr) { + if (key in a) { + if (key in b) { + if (a[key] > b[key]) { + return 1; + } else if (a[key] < b[key]) { + return -1; + } + } else { + return 1; + } + } else if (key in b) { + return -1; + } + } + return 0; + } +} + /** * Priority sort function + * Sort object properties by keys and arrays of objects by key of the items * @param jsonProp * @param sortPriority * @param options * @returns {*} */ function prioritySort(jsonProp, sortPriority, options) { - return sortObjectByKeyNameList(jsonProp, propComparator(sortPriority)) + if (Array.isArray(jsonProp)) { + return sortArrayByItemProps(jsonProp, objComparator(sortPriority)) + } else { + return sortObjectByKeyNameList(jsonProp, propComparator(sortPriority)) + } } /**