Skip to content

Commit c87280f

Browse files
committed
Revert default values being non-nullable; add --default-non-nullable flag
#613
1 parent e2fb371 commit c87280f

35 files changed

+4927
-6440
lines changed

README.md

+9-8
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,15 @@ For anything more complicated, or for generating specs dynamically, you can also
106106

107107
#### CLI Options
108108

109-
| Option | Alias | Default | Description |
110-
| :----------------------------- | :---- | :------: | :--------------------------------------------------------------- |
111-
| `--output [location]` | `-o` | (stdout) | Where should the output file be saved? |
112-
| `--auth [token]` | | | (optional) Provide an auth token to be passed along in the request (only if accessing a private schema). |
113-
| `--immutable-types` | | `false` | (optional) Generates immutable types (readonly properties and readonly array). |
114-
| `--additional-properties` | `-ap` | `false` | (optional) Allow arbitrary properties for all schema objects without `additionalProperties: false` |
115-
| `--prettier-config [location]` | | | (optional) Path to your custom Prettier configuration for output |
116-
| `--raw-schema` | | `false` | Generate TS types from partial schema (e.g. having `components.schema` at the top level) |
109+
| Option | Alias | Default | Description |
110+
| :----------------------------- | :---- | :------: | :------------------------------------------------------------------------------------------------------ |
111+
| `--output [location]` | `-o` | (stdout) | Where should the output file be saved? |
112+
| `--auth [token]` | | | (optional) Provide an auth token to be passed along in the request (only if accessing a private schema) |
113+
| `--immutable-types` | `-it` | `false` | (optional) Generates immutable types (readonly properties and readonly array) |
114+
| `--additional-properties` | `-ap` | `false` | (optional) Allow arbitrary properties for all schema objects without `additionalProperties: false` |
115+
| `--default-non-nullable` | | `false` | (optional) Treat schema objects with default values as non-nullable |
116+
| `--prettier-config [location]` | `-c` | | (optional) Path to your custom Prettier configuration for output |
117+
| `--raw-schema` | | `false` | Generate TS types from partial schema (e.g. having `components.schema` at the top level) |
117118

118119
### 🐢 Node
119120

bin/cli.js

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Options
1818
--auth (optional) Provide an authentication token for private URL
1919
--immutable-types, -it (optional) Generates immutable types (readonly properties and readonly array)
2020
--additional-properties, -ap (optional) Allow arbitrary properties for all schema objects without "additionalProperties: false"
21+
--default-non-nullable (optional) If a schema object has a default value set, don’t mark it as nullable
2122
--prettier-config, -c (optional) specify path to Prettier config file
2223
--raw-schema (optional) Parse as partial schema (raw components)
2324
--version (optional) Force schema parsing version
@@ -35,6 +36,9 @@ Options
3536
type: "boolean",
3637
alias: "it",
3738
},
39+
defaultNonNullable: {
40+
type: "boolean",
41+
},
3842
additionalProperties: {
3943
type: "boolean",
4044
alias: "ap",
@@ -82,6 +86,7 @@ async function generateSchema(pathToSpec) {
8286
auth: cli.flags.auth,
8387
additionalProperties: cli.flags.additionalProperties,
8488
immutableTypes: cli.flags.immutableTypes,
89+
defaultNonNullable: cli.flags.defaultNonNullable,
8590
prettierConfig: cli.flags.prettierConfig,
8691
rawSchema: cli.flags.rawSchema,
8792
version: cli.flags.version,

package-lock.json

+4,426-5,990
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@
7171
"eslint": "^7.26.0",
7272
"eslint-config-prettier": "^8.3.0",
7373
"eslint-plugin-prettier": "^3.4.0",
74-
"jest": "^26.6.3",
75-
"ts-jest": "^26.5.6",
74+
"jest": "^27.0.3",
75+
"ts-jest": "^27.0.2",
7676
"typescript": "^4.2.4"
7777
}
7878
}

src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ export default function openapiTS(
2020
): string {
2121
// 1. set up context
2222
const ctx: GlobalContext = {
23-
auth: options.auth,
2423
additionalProperties: options.additionalProperties || false,
24+
auth: options.auth,
25+
defaultNonNullable: options.defaultNonNullable || false,
2526
formatter: typeof options.formatter === "function" ? options.formatter : undefined,
2627
immutableTypes: options.immutableTypes || false,
2728
rawSchema: options.rawSchema || false,

src/transform/schema.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ export function transformSchemaObjMap(obj: Record<string, any>, options: Transfo
3232

3333
// 2. name (with “?” if optional property)
3434
const readonly = tsReadonly(options.immutableTypes);
35-
const required = options.required.has(k) || hasDefaultValue(v.schema || v) ? "" : "?";
35+
const required =
36+
options.required.has(k) || (options.defaultNonNullable && hasDefaultValue(v.schema || v)) ? "" : "?";
3637
output += `${readonly}"${k}"${required}: `;
3738

3839
// 3. transform

src/types.ts

+3
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ export interface SwaggerToTSOptions {
124124
formatter?: SchemaFormatter;
125125
/** Generates immutable types (readonly properties and readonly array) */
126126
immutableTypes?: boolean;
127+
/** (optional) Treat schema objects with default values as non-nullable */
128+
defaultNonNullable?: boolean;
127129
/** (optional) Path to Prettier config */
128130
prettierConfig?: string;
129131
/** (optional) Parsing input document as raw schema rather than OpenAPI document */
@@ -138,6 +140,7 @@ export interface GlobalContext {
138140
auth?: string;
139141
formatter?: SchemaFormatter;
140142
immutableTypes: boolean;
143+
defaultNonNullable: boolean;
141144
/** (optional) Should logging be suppressed? (necessary for STDOUT) */
142145
silent?: boolean;
143146
namespace?: string;

tests/bin/expected/manifold.ts

+19-19
Original file line numberDiff line numberDiff line change
@@ -618,11 +618,11 @@ export interface definitions {
618618
base_url?: string;
619619
sso_url?: string;
620620
version?: "v1";
621-
features: {
621+
features?: {
622622
access_code?: boolean;
623623
sso?: boolean;
624624
plan_change?: boolean;
625-
credential: "none" | "single" | "multiple" | "unknown";
625+
credential?: "none" | "single" | "multiple" | "unknown";
626626
};
627627
};
628628
/** An array of platform ids to restrict this product for. */
@@ -662,24 +662,24 @@ export interface definitions {
662662
name: definitions["Name"];
663663
type: "boolean" | "string" | "number";
664664
/** This sets whether or not the feature can be customized by a consumer. */
665-
customizable: boolean;
665+
customizable?: boolean;
666666
/**
667667
* This sets whether or not the feature can be upgraded by the consumer after the
668668
* resource has provisioned. Upgrading means setting a higher value or selecting a
669669
* higher element in the list.
670670
*/
671-
upgradable: boolean;
671+
upgradable?: boolean;
672672
/**
673673
* This sets whether or not the feature can be downgraded by the consumer after the
674674
* resource has provisioned. Downgrading means setting a lower value or selecting a
675675
* lower element in the list.
676676
*/
677-
downgradable: boolean;
677+
downgradable?: boolean;
678678
/**
679679
* Sets if this feature’s value is trackable from the provider,
680680
* this only really affects numeric constraints.
681681
*/
682-
measurable: boolean;
682+
measurable?: boolean;
683683
values?: definitions["FeatureValuesList"];
684684
};
685685
/**
@@ -699,7 +699,7 @@ export interface definitions {
699699
* is selected or is default for the plan.
700700
* Cost is deprecated in favor of the `price.cost` field.
701701
*/
702-
cost: number;
702+
cost?: number;
703703
/**
704704
* Price describes the cost of a feature. It should be preferred over
705705
* the `cost` property.
@@ -710,13 +710,13 @@ export interface definitions {
710710
* when this value is selected or is default for the plan.
711711
* Number features should use the cost range instead.
712712
*/
713-
cost: number;
713+
cost?: number;
714714
/**
715715
* When a feature is used to multiply the cost of the plan or of
716716
* another feature, multiply factor is used for calculation.
717717
* A feature cannot have both a cost and a multiply factor.
718718
*/
719-
multiply_factor: number;
719+
multiply_factor?: number;
720720
/** Price describes how the feature cost should be calculated. */
721721
formula?: definitions["PriceFormula"];
722722
/** Description explains how a feature is calculated to the user. */
@@ -737,9 +737,9 @@ export interface definitions {
737737
* means this numeric details has no scale, and will not be or customizable.
738738
* Some plans may not have a measureable or customizable feature.
739739
*/
740-
increment: number;
740+
increment?: number;
741741
/** Minimum value that can be set by a user if customizable */
742-
min: number;
742+
min?: number;
743743
/** Maximum value that can be set by a user if customizable */
744744
max?: number;
745745
/** Applied to the end of the number for display, for example the ‘GB’ in ‘20 GB’. */
@@ -758,7 +758,7 @@ export interface definitions {
758758
* An integer in 10,000,000ths of cents, will be multiplied by the
759759
* numeric value set in the feature to determine the cost.
760760
*/
761-
cost_multiple: number;
761+
cost_multiple?: number;
762762
};
763763
FeatureValue: {
764764
feature: definitions["Label"];
@@ -784,40 +784,40 @@ export interface definitions {
784784
* When true, everyone can see the product when requested. When false it will
785785
* not be visible to anyone except those on the provider team.
786786
*/
787-
public: boolean;
787+
public?: boolean;
788788
/**
789789
* When true, the product will be displayed in product listings alongside
790790
* other products. When false the product will be excluded from listings,
791791
* but can still be provisioned directly if it's label is known.
792792
* Any pages that display information about the product when not listed,
793793
* should indicate to webcrawlers that the content should not be indexed.
794794
*/
795-
listed: boolean;
795+
listed?: boolean;
796796
/**
797797
* Object to hold various flags for marketing purposes only. These are values
798798
* that need to be stored, but should not affect decision making in code. If
799799
* we find ourselves in a position where we think they should, we should
800800
* consider refactoring our listing definition.
801801
*/
802-
marketing: {
802+
marketing?: {
803803
/**
804804
* Indicates whether or not the product is in `Beta` and should be
805805
* advertised as such. This does not have any impact on who can access the
806806
* product, it is just used to inform consumers through our clients.
807807
*/
808-
beta: boolean;
808+
beta?: boolean;
809809
/**
810810
* Indicates whether or not the product is in `New` and should be
811811
* advertised as such. This does not have any impact on who can access the
812812
* product, it is just used to inform consumers through our clients.
813813
*/
814-
new: boolean;
814+
new?: boolean;
815815
/**
816816
* Indicates whether or not the product is in `New` and should be
817817
* advertised as such. This does not have any impact on who can access the
818818
* product, it is just used to inform consumers through our clients.
819819
*/
820-
featured: boolean;
820+
featured?: boolean;
821821
};
822822
};
823823
/**
@@ -858,7 +858,7 @@ export interface definitions {
858858
* * `multiple`: Multiple credentials are supported at the same time.
859859
* * `unknown`: The credential type is unknown.
860860
*/
861-
credential: "none" | "single" | "multiple" | "unknown";
861+
credential?: "none" | "single" | "multiple" | "unknown";
862862
};
863863
ProductBody: {
864864
provider_id: definitions["ID"];

tests/bin/expected/petstore.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export interface components {
7272
shipDate?: string;
7373
/** Order Status */
7474
status?: "placed" | "approved" | "delivered";
75-
complete: boolean;
75+
complete?: boolean;
7676
};
7777
Category: {
7878
id?: number;

tests/bin/expected/prettier-js.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export interface components {
7272
shipDate?: string
7373
/** Order Status */
7474
status?: 'placed' | 'approved' | 'delivered'
75-
complete: boolean
75+
complete?: boolean
7676
}
7777
Category: {
7878
id?: number

tests/bin/expected/prettier-json.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export interface components {
7272
shipDate?: string
7373
/** Order Status */
7474
status?: 'placed' | 'approved' | 'delivered'
75-
complete: boolean
75+
complete?: boolean
7676
}
7777
Category: {
7878
id?: number

tests/bin/expected/stdout.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export interface components {
7272
shipDate?: string;
7373
/** Order Status */
7474
status?: "placed" | "approved" | "delivered";
75-
complete: boolean;
75+
complete?: boolean;
7676
};
7777
Category: {
7878
id?: number;

tests/operation.test.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { transformOperationObj } from "../src/transform/operation";
22

3-
const defaults = { additionalProperties: false, immutableTypes: false, rawSchema: false };
3+
const defaults = {
4+
additionalProperties: false,
5+
immutableTypes: false,
6+
defaultNonNullable: false,
7+
rawSchema: false,
8+
};
49

510
describe("requestBody", () => {
611
const basicSchema = {

tests/parameters.test.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { transformParametersArray } from "../src/transform/parameters";
22

3-
const defaults = { additionalProperties: false, immutableTypes: false, rawSchema: false };
3+
const defaults = {
4+
additionalProperties: false,
5+
immutableTypes: false,
6+
defaultNonNullable: false,
7+
rawSchema: false,
8+
};
49

510
describe("transformParametersArray()", () => {
611
describe("v2", () => {

tests/paths.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const defaults = {
99
additionalProperties: false,
1010
globalParameters: {},
1111
immutableTypes: false,
12+
defaultNonNullable: false,
1213
operations: {},
1314
rawSchema: false,
1415
version: 3, // both 2 and 3 should generate the same

tests/request.test.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import prettier from "prettier";
22
import { transformRequestBodies } from "../src/transform/request";
33

4-
const defaults = { additionalProperties: false, immutableTypes: false, rawSchema: false };
4+
const defaults = {
5+
additionalProperties: false,
6+
immutableTypes: false,
7+
defaultNonNullable: false,
8+
rawSchema: false,
9+
};
510

611
function format(source: string) {
712
return prettier.format(`type requestBodies = {${source.trim()}}`, { parser: "typescript" });

tests/schema.test.ts

+24
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { transformSchemaObj as transform } from "../src/transform/schema";
77
const defaults = {
88
additionalProperties: false,
99
immutableTypes: false,
10+
defaultNonNullable: false,
1011
required: new Set<string>(),
1112
rawSchema: false,
1213
version: 3,
@@ -368,6 +369,29 @@ describe("SchemaObject", () => {
368369
/** user photo */
369370
"avatar"?: string;
370371
372+
}`);
373+
});
374+
});
375+
376+
describe("--default-non-nullable", () => {
377+
it("default: objects with default values are nullable", () => {
378+
expect(
379+
transform({ type: "object", properties: { default: { type: "boolean", default: true } } }, { ...defaults })
380+
).toBe(`{
381+
"default"?: boolean;
382+
383+
}`);
384+
});
385+
386+
it("enabled: objects with default values are non-nullable", () => {
387+
expect(
388+
transform(
389+
{ type: "object", properties: { default: { type: "boolean", default: true } } },
390+
{ ...defaults, defaultNonNullable: true }
391+
)
392+
).toBe(`{
393+
"default": boolean;
394+
371395
}`);
372396
});
373397
});

0 commit comments

Comments
 (0)