From 47af2daf4f5de660bac23c70e0d0b818e9dac01a Mon Sep 17 00:00:00 2001 From: Dan Enman Date: Thu, 21 Feb 2019 19:42:21 -0400 Subject: [PATCH 1/4] Create interfaces for additionalProperties objects --- src/swagger-2.ts | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/swagger-2.ts b/src/swagger-2.ts index 4a845ff51..6af1a2c3f 100644 --- a/src/swagger-2.ts +++ b/src/swagger-2.ts @@ -9,6 +9,7 @@ export interface Swagger2Definition { items?: Swagger2Definition; oneOf?: Swagger2Definition[]; properties?: { [index: string]: Swagger2Definition }; + additionalProperties?: boolean | Swagger2Definition; required?: string[]; type?: 'array' | 'boolean' | 'integer' | 'number' | 'object' | 'string'; } @@ -113,7 +114,7 @@ function parse(spec: Swagger2, namespace: string) { function buildNextInterface() { const nextObject = queue.pop(); if (!nextObject) return; // Geez TypeScript it’s going to be OK - const [ID, { allOf, properties, required }] = nextObject; + const [ID, { allOf, properties, required, additionalProperties, type }] = nextObject; let allProperties = properties || {}; const includes: string[] = []; @@ -132,7 +133,12 @@ function parse(spec: Swagger2, namespace: string) { } // If nothing’s here, let’s skip this one. - if (!Object.keys(allProperties).length) { + if ( + !Object.keys(allProperties).length && + additionalProperties !== true && + type && + TYPES[type] + ) { return; } // Open interface @@ -162,6 +168,17 @@ function parse(spec: Swagger2, namespace: string) { output.push(`${name}: ${type};`); }); + if (additionalProperties) { + if (additionalProperties === true) { + output.push(`[name: string]: ${ID}`); + } + + if ((additionalProperties).type) { + const type = getType(additionalProperties, ''); + output.push(`[name: string]: ${type}`); + } + } + // Close interface output.push('}'); @@ -181,7 +198,7 @@ function parse(spec: Swagger2, namespace: string) { output.push('}'); // Close namespace - return prettier.format(output.join('\n'), { parser: 'typescript' }); + return prettier.format(output.join('\n'), { parser: 'typescript', singleQuote: true }); } export default parse; From b357b5ac3817498aade1654260ef65b9b594a0e4 Mon Sep 17 00:00:00 2001 From: Dan Enman Date: Fri, 22 Feb 2019 09:27:55 -0400 Subject: [PATCH 2/4] Use `any` for undefined additionalProperties type --- src/swagger-2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/swagger-2.ts b/src/swagger-2.ts index 6af1a2c3f..ebdafbca5 100644 --- a/src/swagger-2.ts +++ b/src/swagger-2.ts @@ -170,7 +170,7 @@ function parse(spec: Swagger2, namespace: string) { if (additionalProperties) { if (additionalProperties === true) { - output.push(`[name: string]: ${ID}`); + output.push(`[name: string]: any`); } if ((additionalProperties).type) { From 0aeefa006334dc7112ca2ec210a2096fffb0098e Mon Sep 17 00:00:00 2001 From: Dan Enman Date: Fri, 22 Feb 2019 09:28:15 -0400 Subject: [PATCH 3/4] Update example with additionalProperties --- example/input.yaml | 19 ++++++++++++++++++- example/output.ts | 43 ++++++++++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/example/input.yaml b/example/input.yaml index 71e00a939..d5009df99 100644 --- a/example/input.yaml +++ b/example/input.yaml @@ -1544,4 +1544,21 @@ definitions: - version - type - body - - provider \ No newline at end of file + - provider + + Credentials: + type: object + description: | + Map of configuration variable names to values. Names must match + `^[a-zA-Z][a-zA-Z0-9_]{0,999}$`. + additionalProperties: + type: string + FeatureMap: + type: object + description: A map of feature labels to selected values for customizable features + additionalProperties: true + x-go-type: + type: FeatureMap + import: + package: github.com/manifoldco/go-manifold + alias: manifold \ No newline at end of file diff --git a/example/output.ts b/example/output.ts index a8da9ecce..33c2f5aa5 100644 --- a/example/output.ts +++ b/example/output.ts @@ -43,7 +43,7 @@ namespace OpenAPI2 { features?: ProductIntegrationFeatures; } export enum UpdateProductBodyIntegrationVersion { - V1 = "v1" + V1 = 'v1' } export interface UpdateProduct { id: string; @@ -87,7 +87,7 @@ namespace OpenAPI2 { Version1 = 1 } export enum RegionType { - Region = "region" + Region = 'region' } export interface ProviderBody { teamId: string; @@ -104,11 +104,12 @@ namespace OpenAPI2 { body: ProviderBody; } export enum ProviderType { - Provider = "provider" + Provider = 'provider' } export enum ProviderVersion { Version1 = 1 } + export interface ProductTags {} export interface ProductListing { // When true, everyone can see the product when requested. When false it will // not be visible to anyone except those on the provider team. @@ -155,8 +156,8 @@ namespace OpenAPI2 { region?: ProductIntegrationFeaturesRegion; } export enum ProductIntegrationFeaturesRegion { - UserSpecified = "user-specified", - Unspecified = "unspecified" + UserSpecified = 'user-specified', + Unspecified = 'unspecified' } export interface ProductBody { providerId: string; @@ -189,19 +190,19 @@ namespace OpenAPI2 { features: ProductIntegrationFeatures; } export enum ProductBodyIntegrationVersion { - V1 = "v1" + V1 = 'v1' } export interface ProductBodyBilling { type: ProductBodyBillingType; currency: ProductBodyBillingCurrency; } export enum ProductBodyBillingCurrency { - Usd = "usd" + Usd = 'usd' } export enum ProductBodyBillingType { - MonthlyProrated = "monthly-prorated", - MonthlyAnniversary = "monthly-anniversary", - AnnualAnniversary = "annual-anniversary" + MonthlyProrated = 'monthly-prorated', + MonthlyAnniversary = 'monthly-anniversary', + AnnualAnniversary = 'annual-anniversary' } export interface ProductBodyTerms { url?: string; @@ -214,11 +215,12 @@ namespace OpenAPI2 { body: ProductBody; } export enum ProductType { - Product = "product" + Product = 'product' } export enum ProductVersion { Version1 = 1 } + export interface PlanResizeList {} export interface PlanBody { providerId: string; productId: string; @@ -244,11 +246,12 @@ namespace OpenAPI2 { body: PlanBody; } export enum PlanType { - Plan = "plan" + Plan = 'plan' } export enum PlanVersion { Version1 = 1 } + export interface FeatureValuesList {} export interface FeatureValueDetails { label: string; name: string; @@ -299,9 +302,9 @@ namespace OpenAPI2 { values?: FeatureValueDetails[]; } export enum FeatureTypeType { - Boolean = "boolean", - String = "string", - Number = "number" + Boolean = 'boolean', + String = 'string', + Number = 'number' } export interface FeatureNumericRange { // Defines the end of the range ( inclusive ), from the previous, or 0; @@ -329,6 +332,9 @@ namespace OpenAPI2 { suffix?: string; costRanges?: FeatureNumericRange[]; } + export interface FeatureMap { + [name: string]: any; + } export interface ExpandedProduct { id: string; version: ExpandedProductVersion; @@ -338,7 +344,7 @@ namespace OpenAPI2 { provider: Provider; } export enum ExpandedProductType { - Product = "product" + Product = 'product' } export enum ExpandedProductVersion { Version1 = 1 @@ -360,7 +366,7 @@ namespace OpenAPI2 { body: ExpandedPlanBody; } export enum ExpandedPlanType { - Plan = "plan" + Plan = 'plan' } export enum ExpandedPlanVersion { Version1 = 1 @@ -376,6 +382,9 @@ namespace OpenAPI2 { // Explanation of the errors message: string[]; } + export interface Credentials { + [name: string]: string; + } export interface CreateRegion { body: RegionBody; } From be775ce36262f0f49d1064423e8712f9ac1a4ea1 Mon Sep 17 00:00:00 2001 From: Dan Enman Date: Fri, 22 Feb 2019 10:38:26 -0400 Subject: [PATCH 4/4] Add additionalProperties tests --- tests/swagger-2.test.ts | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/tests/swagger-2.test.ts b/tests/swagger-2.test.ts index 1a4330984..593241eb0 100644 --- a/tests/swagger-2.test.ts +++ b/tests/swagger-2.test.ts @@ -8,7 +8,7 @@ import { Swagger2 } from '../src/swagger-2'; // Let Prettier handle formatting, not the test expectations function format(spec: string, namespaced?: boolean) { const wrapped = namespaced === false ? spec : `namespace OpenAPI2 { ${spec} }`; - return prettier.format(wrapped, { parser: 'typescript' }); + return prettier.format(wrapped, { parser: 'typescript', singleQuote: true }); } describe('Swagger 2 spec', () => { @@ -249,6 +249,44 @@ describe('Swagger 2 spec', () => { }); }); + it('can deal with additionalProperties: true', () => { + const swagger: Swagger2 = { + definitions: { + FeatureMap: { + type: 'object', + additionalProperties: true, + }, + }, + }; + + const ts = format(` + export interface FeatureMap { + [name: string]: any; + }`); + + expect(swaggerToTS(swagger)).toBe(ts); + }); + + it('can deal with additionalProperties of type', () => { + const swagger: Swagger2 = { + definitions: { + Credentials: { + type: 'object', + additionalProperties: { + type: 'string', + }, + }, + }, + }; + + const ts = format(` + export interface Credentials { + [name: string]: string; + }`); + + expect(swaggerToTS(swagger)).toBe(ts); + }); + describe('other output', () => { it('generates the example output correctly', () => { const input = yaml.safeLoad(