diff --git a/images/ts-inverse.svg b/images/ts-inverse.svg new file mode 100644 index 00000000..1763247d --- /dev/null +++ b/images/ts-inverse.svg @@ -0,0 +1,10 @@ + + + + Layer 1 + + + + + + \ No newline at end of file diff --git a/images/ts.svg b/images/ts.svg new file mode 100644 index 00000000..337a627a --- /dev/null +++ b/images/ts.svg @@ -0,0 +1,8 @@ + + + + Layer 1 + + + + \ No newline at end of file diff --git a/package.json b/package.json index fa3c2788..28ace453 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,8 @@ "onCommand:rest-client.clear-history", "onCommand:rest-client.save-response", "onCommand:rest-client.save-response-body", - "onCommand:rest-client.copy-response-body", + "onCommand:rest-client.copy-response", + "onCommand:rest-client.copy-response-type", "onCommand:rest-client.switch-environment", "onCommand:rest-client.clear-aad-token-cache", "onCommand:rest-client.cancel-request", @@ -138,14 +139,23 @@ "category": "Rest Client" }, { - "command": "rest-client.copy-response-body", - "title": "Copy Response Body", + "command": "rest-client.copy-response", + "title": "Copy Response", "icon": { "light": "./images/copy.svg", "dark": "./images/copy-inverse.svg" }, "category": "Rest Client" }, + { + "command": "rest-client.copy-response-type", + "title": "Copy Response type", + "icon": { + "light": "./images/ts.svg", + "dark": "./images/ts-inverse.svg" + }, + "category": "Rest Client" + }, { "command": "rest-client.generate-codesnippet", "title": "Generate Code Snippet", @@ -200,7 +210,11 @@ "when": "httpResponsePreviewFocus" }, { - "command": "rest-client.copy-response-body", + "command": "rest-client.copy-response", + "when": "httpResponsePreviewFocus" + }, + { + "command": "rest-client.copy-response-type", "when": "httpResponsePreviewFocus" }, { @@ -211,19 +225,24 @@ "editor/title": [ { "when": "httpResponsePreviewFocus", - "command": "rest-client.save-response", + "command": "rest-client.copy-response-type", "group": "navigation@1" }, { "when": "httpResponsePreviewFocus", - "command": "rest-client.save-response-body", + "command": "rest-client.save-response", "group": "navigation@2" }, { "when": "httpResponsePreviewFocus", - "command": "rest-client.copy-response-body", + "command": "rest-client.save-response-body", "group": "navigation@3" }, + { + "when": "httpResponsePreviewFocus", + "command": "rest-client.copy-response", + "group": "navigation@4" + }, { "when": "httpResponsePreviewFocus", "command": "rest-client.fold-response", @@ -633,7 +652,8 @@ "vscode:prepublish": "webpack --mode production", "webpack": "webpack --mode development", "watch": "webpack --mode development --watch --info-verbosity verbose", - "tslint": "tslint --project tsconfig.json" + "tslint": "tslint --project tsconfig.json", + "build:vsix":"vsce package" }, "devDependencies": { "@types/aws4": "^1.5.1", diff --git a/src/utils/json2ts.ts b/src/utils/json2ts.ts new file mode 100644 index 00000000..88d76e8a --- /dev/null +++ b/src/utils/json2ts.ts @@ -0,0 +1,132 @@ +/** + * @Author beshanoe + * Latest commit a8b0e42 on 5 Jun 2017 + * https://github.com/beshanoe/json2ts/blob/master/src/utils/json2.ts + */ + +interface IJson2TsConfigPrivate { + prependWithI: boolean; + sortAlphabetically: boolean; + addExport: boolean; + useArrayGeneric: boolean; + optionalFields: boolean; + prefix: string; + rootObjectName: string; +} + +export type IJson2TsConfig = Partial; + +export class Json2Ts { + + private config: IJson2TsConfigPrivate; + + private interfaces: { + [name: string]: { + [field: string]: string; + } + } = {}; + + constructor( + config: IJson2TsConfig = {} + ) { + this.config = { + prependWithI: true, + sortAlphabetically: false, + addExport: false, + useArrayGeneric: false, + optionalFields: false, + prefix: '', + rootObjectName: 'RootObject', + ...config + }; + } + + convert(json: {}) { + this.interfaces = {}; + let result = `\n`; + this.unknownToTS(json); + result += this.interfacesToString(); + return result; + } + + private unknownToTS(value: {}, key: string | undefined = void 0) { + let type: string = typeof value; + if (type === 'object') { + if (Array.isArray(value)) { + type = this.arrayToTS(value, key); + } else { + type = this.objectToTS(value, key && this.capitalizeFirst(key)); + } + } + return type; + } + + private arrayToTS(array: {}[], key: string | undefined = void 0) { + let type = array.length ? void 0 : 'any'; + for (const item of array) { + const itemType = this.unknownToTS(item, this.keyToTypeName(key)); + if (type && itemType !== type) { + type = 'any'; + break; + } else { + type = itemType; + } + } + return this.config.useArrayGeneric ? `Array<${type}>` : `${type}[]`; + } + + private keyToTypeName(key: string | undefined = void 0) { + if (!key || key.length < 2) { + return key; + } + const [first, ...rest]: string[] = Array.prototype.slice.apply(key); + const last = rest.pop(); + return [first.toUpperCase(), ...rest, last === 's' ? '' : last].join(''); + } + + private capitalizeFirst(str: string) { + const [first, ...rest]: string[] = Array.prototype.slice.apply(str); + return [first.toUpperCase(), ...rest].join(''); + } + + private objectToTS(obj: {}, type: string = this.config.rootObjectName) { + if (obj === null) { + return 'any'; + } + const { prependWithI, prefix } = this.config; + if (prependWithI) { + type = `I${prefix || ''}${type}`; + } + if (!this.interfaces[type]) { + this.interfaces[type] = {}; + } + const interfaceName = this.interfaces[type]; + Object.keys(obj).forEach(key => { + const value = obj[key]; + const fieldType = this.unknownToTS(value, key); + if (!interfaceName[key] || interfaceName[key].indexOf('any') === 0) { + interfaceName[key] = fieldType; + } + }); + return type; + } + + private interfacesToString() { + const { sortAlphabetically, addExport, optionalFields } = this.config; + return Object.keys(this.interfaces).map(name => { + const interfaceStr = [`${addExport ? 'export ' : ''}interface ${name} {`]; + const fields = Object.keys(this.interfaces[name]); + if (sortAlphabetically) { + fields.sort(); + } + fields + .forEach(field => { + const type = this.interfaces[name][field]; + interfaceStr.push(` ${field}${optionalFields ? '?' : ''}: ${type};`); + }); + interfaceStr.push('}\n'); + return interfaceStr.join('\n'); + }).join('\n'); + } + +} \ No newline at end of file diff --git a/src/views/httpResponseWebview.ts b/src/views/httpResponseWebview.ts index bb6bae2c..9ee0a4e3 100644 --- a/src/views/httpResponseWebview.ts +++ b/src/views/httpResponseWebview.ts @@ -8,6 +8,7 @@ import { HttpResponse } from '../models/httpResponse'; import { PreviewOption } from '../models/previewOption'; import { trace } from '../utils/decorator'; import { disposeAll } from '../utils/dispose'; +import { Json2Ts } from '../utils/json2ts'; import { MimeUtility } from '../utils/mimeUtility'; import { base64, getHeader, isJSONString } from '../utils/misc'; import { ResponseFormatUtility } from '../utils/responseFormatUtility'; @@ -51,7 +52,8 @@ export class HttpResponseWebview extends BaseWebview { this.context.subscriptions.push(commands.registerCommand('rest-client.fold-response', this.foldResponseBody, this)); this.context.subscriptions.push(commands.registerCommand('rest-client.unfold-response', this.unfoldResponseBody, this)); - this.context.subscriptions.push(commands.registerCommand('rest-client.copy-response-body', this.copyBody, this)); + this.context.subscriptions.push(commands.registerCommand('rest-client.copy-response', this.copyResponse, this)); + this.context.subscriptions.push(commands.registerCommand('rest-client.copy-response-type', this.copyResponseType, this)); this.context.subscriptions.push(commands.registerCommand('rest-client.save-response', this.save, this)); this.context.subscriptions.push(commands.registerCommand('rest-client.save-response-body', this.saveBody, this)); } @@ -123,13 +125,31 @@ export class HttpResponseWebview extends BaseWebview { this.activePanel?.webview.postMessage({ 'command': 'unfoldAll' }); } - @trace('Copy Response Body') - private async copyBody() { + @trace('Copy Response') + private async copyResponse() { if (this.activeResponse) { await this.clipboard.writeText(this.activeResponse.body); } } + @trace('Copy Response type') + private async copyResponseType() { + if (this.activeResponse) { + // await this.clipboard.writeText(this.activeResponse.body); + const config = { + prependWithI: true, + sortAlphabetically: false, + addExport: true, + useArrayGeneric: false, + optionalFields: true, + prefix: '', + rootObjectName: 'RootObject' + }; + const parser = new Json2Ts(config); + await this.clipboard.writeText(parser.convert(JSON.parse(this.activeResponse.body))); + } + } + @trace('Save Response') private async save() { if (this.activeResponse) {