Skip to content

Create interfaces for additionalProperties objects #4

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

Merged
merged 4 commits into from
Feb 22, 2019
Merged
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
19 changes: 18 additions & 1 deletion example/input.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1544,4 +1544,21 @@ definitions:
- version
- type
- body
- provider
- 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
43 changes: 26 additions & 17 deletions example/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ namespace OpenAPI2 {
features?: ProductIntegrationFeatures;
}
export enum UpdateProductBodyIntegrationVersion {
V1 = "v1"
V1 = 'v1'
}
export interface UpdateProduct {
id: string;
Expand Down Expand Up @@ -87,7 +87,7 @@ namespace OpenAPI2 {
Version1 = 1
}
export enum RegionType {
Region = "region"
Region = 'region'
}
export interface ProviderBody {
teamId: string;
Expand All @@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -329,6 +332,9 @@ namespace OpenAPI2 {
suffix?: string;
costRanges?: FeatureNumericRange[];
}
export interface FeatureMap {
[name: string]: any;
}
export interface ExpandedProduct {
id: string;
version: ExpandedProductVersion;
Expand All @@ -338,7 +344,7 @@ namespace OpenAPI2 {
provider: Provider;
}
export enum ExpandedProductType {
Product = "product"
Product = 'product'
}
export enum ExpandedProductVersion {
Version1 = 1
Expand All @@ -360,7 +366,7 @@ namespace OpenAPI2 {
body: ExpandedPlanBody;
}
export enum ExpandedPlanType {
Plan = "plan"
Plan = 'plan'
}
export enum ExpandedPlanVersion {
Version1 = 1
Expand All @@ -376,6 +382,9 @@ namespace OpenAPI2 {
// Explanation of the errors
message: string[];
}
export interface Credentials {
[name: string]: string;
}
export interface CreateRegion {
body: RegionBody;
}
Expand Down
23 changes: 20 additions & 3 deletions src/swagger-2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
Expand Down Expand Up @@ -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[] = [];
Expand All @@ -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
Expand Down Expand Up @@ -162,6 +168,17 @@ function parse(spec: Swagger2, namespace: string) {
output.push(`${name}: ${type};`);
});

if (additionalProperties) {
if (<boolean>additionalProperties === true) {
output.push(`[name: string]: any`);
}

if ((<Swagger2Definition>additionalProperties).type) {
const type = getType(<Swagger2Definition>additionalProperties, '');
output.push(`[name: string]: ${type}`);
}
}

// Close interface
output.push('}');

Expand All @@ -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 });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note: I personally like singleQuote, but since we’re open-sourcing it I didn’t know the best thing to decide on. Maybe we could add options one day to allow customization of this, but this is fine for now (we can always change it if someone asks!)

}

export default parse;
40 changes: 39 additions & 1 deletion tests/swagger-2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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);
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

describe('other output', () => {
it('generates the example output correctly', () => {
const input = yaml.safeLoad(
Expand Down