Skip to content

Commit c00448e

Browse files
author
Daniel Playfair Cal
committed
fix: account for children making parent fields required
1 parent 3ff93cf commit c00448e

File tree

2 files changed

+116
-59
lines changed

2 files changed

+116
-59
lines changed

src/schema.js

Lines changed: 55 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,7 @@ const getTypeAlias = (rawSchema) => {
6363
};
6464

6565
const getEnumNames = (schema) => {
66-
return (
67-
schema["x-enumNames"] ||
68-
schema["xEnumNames"] ||
69-
schema["x-enumnames"] ||
70-
schema["x-enum-varnames"]
71-
);
66+
return schema["x-enumNames"] || schema["xEnumNames"] || schema["x-enumnames"] || schema["x-enum-varnames"];
7267
};
7368

7469
const getInternalSchemaType = (schema) => {
@@ -151,9 +146,7 @@ const getObjectTypeContent = (schema) => {
151146
!_.isUndefined(property.maximum) && `@max ${property.maximum}`,
152147
!_.isUndefined(property.pattern) && `@pattern ${property.pattern}`,
153148
!_.isUndefined(property.example) &&
154-
`@example ${
155-
_.isObject(property.example) ? JSON.stringify(property.example) : property.example
156-
}`,
149+
`@example ${_.isObject(property.example) ? JSON.stringify(property.example) : property.example}`,
157150
]).join("\n"),
158151
isRequired: required,
159152
isNullable: nullable,
@@ -178,44 +171,69 @@ const getObjectTypeContent = (schema) => {
178171
const complexTypeGetter = (schema) => getInlineParseContent(schema);
179172
const filterContents = (contents, types) => _.filter(contents, (type) => !_.includes(types, type));
180173

174+
const makeAddRequiredToChildSchema = (parentSchema) => (childSchema) => {
175+
let required = childSchema.required || [];
176+
let properties = childSchema.properties || {};
177+
178+
// Inherit all the required fields from the parent schema that are defined
179+
// either on the parent schema or on the child schema
180+
// TODO: any that are defined at grandparents or higher are ignored
181+
required = required.concat(
182+
(parentSchema.required || []).filter(
183+
(key) =>
184+
!required.includes(key) && (_.keys(properties).includes(key) || _.keys(parentSchema.properties).includes(key)),
185+
),
186+
);
187+
188+
// Identify properties that are required in the child schema, but
189+
// defined only in the parent schema (TODO: this only works one level deep)
190+
const parentPropertiesRequiredByChild = required.filter(
191+
(key) => !_.keys(childSchema.properties).includes(key) && _.keys(parentSchema.properties).includes(key),
192+
);
193+
194+
// Add such properties to the child so that they can be overriden and made required
195+
properties = {
196+
...properties,
197+
...parentPropertiesRequiredByChild.reduce(
198+
(additionalProperties, key) => ({
199+
...additionalProperties,
200+
[key]: (parentSchema.properties || {})[key],
201+
}),
202+
{},
203+
),
204+
};
205+
206+
return _.merge(
207+
{
208+
required: required,
209+
properties: properties,
210+
},
211+
childSchema,
212+
);
213+
};
214+
181215
const complexSchemaParsers = {
182216
[SCHEMA_TYPES.COMPLEX_ONE_OF]: (schema) => {
183217
// T1 | T2
184-
const combined = _.map(
185-
schema.oneOf.map((s) => _.merge({ required: schema.required }, s)),
186-
complexTypeGetter,
187-
);
218+
const combined = _.map(schema.oneOf.map(makeAddRequiredToChildSchema(schema)), complexTypeGetter);
188219

189220
return checkAndAddNull(schema, filterContents(combined, [TS_KEYWORDS.ANY]).join(" | "));
190221
},
191222
[SCHEMA_TYPES.COMPLEX_ALL_OF]: (schema) => {
192223
// T1 & T2
193-
const combined = _.map(
194-
schema.allOf.map((s) => _.merge({ required: schema.required }, s)),
195-
complexTypeGetter,
196-
);
224+
const combined = _.map(schema.allOf.map(makeAddRequiredToChildSchema(schema)), complexTypeGetter);
197225
return checkAndAddNull(
198226
schema,
199-
filterContents(combined, [...JS_EMPTY_TYPES, ...JS_PRIMITIVE_TYPES, TS_KEYWORDS.ANY]).join(
200-
" & ",
201-
),
227+
filterContents(combined, [...JS_EMPTY_TYPES, ...JS_PRIMITIVE_TYPES, TS_KEYWORDS.ANY]).join(" & "),
202228
);
203229
},
204230
[SCHEMA_TYPES.COMPLEX_ANY_OF]: (schema) => {
205231
// T1 | T2 | (T1 & T2)
206-
const combined = _.map(
207-
schema.anyOf.map((s) => _.merge({ required: schema.required }, s)),
208-
complexTypeGetter,
209-
);
210-
const nonEmptyTypesCombined = filterContents(combined, [
211-
...JS_EMPTY_TYPES,
212-
...JS_PRIMITIVE_TYPES,
213-
TS_KEYWORDS.ANY,
214-
]);
232+
const combined = _.map(schema.anyOf.map(makeAddRequiredToChildSchema(schema)), complexTypeGetter);
233+
const nonEmptyTypesCombined = filterContents(combined, [...JS_EMPTY_TYPES, ...JS_PRIMITIVE_TYPES, TS_KEYWORDS.ANY]);
215234
return checkAndAddNull(
216235
schema,
217-
`${combined.join(" | ")}` +
218-
(nonEmptyTypesCombined.length > 1 ? ` | (${nonEmptyTypesCombined.join(" & ")})` : ""),
236+
`${combined.join(" | ")}` + (nonEmptyTypesCombined.length > 1 ? ` | (${nonEmptyTypesCombined.join(" & ")})` : ""),
219237
);
220238
},
221239
// TODO
@@ -236,8 +254,7 @@ const getComplexType = (schema) => {
236254
};
237255

238256
const attachParsedRef = (originalSchema, parsedSchema) => {
239-
const parsedSchemaAfterHook =
240-
config.hooks.onParseSchema(originalSchema, parsedSchema) || parsedSchema;
257+
const parsedSchemaAfterHook = config.hooks.onParseSchema(originalSchema, parsedSchema) || parsedSchema;
241258

242259
if (originalSchema) {
243260
originalSchema.$parsed = parsedSchemaAfterHook;
@@ -272,12 +289,7 @@ const schemaParsers = {
272289
return {
273290
key: formattedKey,
274291
type: keyType,
275-
value:
276-
enumValue === null
277-
? enumValue
278-
: isIntegerOrBooleanEnum
279-
? `${enumValue}`
280-
: `"${enumValue}"`,
292+
value: enumValue === null ? enumValue : isIntegerOrBooleanEnum ? `${enumValue}` : `"${enumValue}"`,
281293
};
282294
});
283295
} else {
@@ -299,9 +311,7 @@ const schemaParsers = {
299311
type: SCHEMA_TYPES.ENUM,
300312
keyType: keyType,
301313
typeIdentifier:
302-
config.generateUnionEnums || (!enumNames && isIntegerOrBooleanEnum)
303-
? TS_KEYWORDS.TYPE
304-
: TS_KEYWORDS.ENUM,
314+
config.generateUnionEnums || (!enumNames && isIntegerOrBooleanEnum) ? TS_KEYWORDS.TYPE : TS_KEYWORDS.ENUM,
305315
name: typeName,
306316
description: formatDescription(schema.description),
307317
content,
@@ -340,8 +350,7 @@ const schemaParsers = {
340350
content:
341351
_.compact([
342352
complexSchemaContent && `(${complexSchemaContent})`,
343-
getInternalSchemaType(simpleSchema) === TS_KEYWORDS.OBJECT &&
344-
getInlineParseContent(simpleSchema),
353+
getInternalSchemaType(simpleSchema) === TS_KEYWORDS.OBJECT && getInlineParseContent(simpleSchema),
345354
]).join(" & ") || TS_KEYWORDS.ANY,
346355
});
347356
},
@@ -409,10 +418,7 @@ const parseSchema = (rawSchema, typeName, formattersMap) => {
409418
parsedSchema = schemaParsers[schemaType](fixedRawSchema, typeName);
410419
}
411420

412-
return (
413-
(formattersMap && formattersMap[schemaType] && formattersMap[schemaType](parsedSchema)) ||
414-
parsedSchema
415-
);
421+
return (formattersMap && formattersMap[schemaType] && formattersMap[schemaType](parsedSchema)) || parsedSchema;
416422
};
417423

418424
const parseSchemas = (components) =>
@@ -421,8 +427,7 @@ const parseSchemas = (components) =>
421427
const getInlineParseContent = (rawTypeData, typeName = null) =>
422428
parseSchema(rawTypeData, typeName, inlineExtraFormatters).content;
423429

424-
const getParseContent = (rawTypeData, typeName = null) =>
425-
parseSchema(rawTypeData, typeName).content;
430+
const getParseContent = (rawTypeData, typeName = null) => parseSchema(rawTypeData, typeName).content;
426431

427432
module.exports = {
428433
types,

tests/generated/v3.0/full-swagger-scheme.ts

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10636,9 +10636,41 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
1063610636
*/
1063710637
gistsUpdate: (
1063810638
gistId: string,
10639-
data: (any | any | null) & {
10639+
data: (
10640+
| { description: string }
10641+
| {
10642+
files: Record<
10643+
string,
10644+
(
10645+
| { content: string }
10646+
| { filename: string | null }
10647+
| object
10648+
| ({ content: string } & { filename: string | null } & object)
10649+
) & { content?: string; filename?: string | null }
10650+
>;
10651+
}
10652+
| ({ description: string } & {
10653+
files: Record<
10654+
string,
10655+
(
10656+
| { content: string }
10657+
| { filename: string | null }
10658+
| object
10659+
| ({ content: string } & { filename: string | null } & object)
10660+
) & { content?: string; filename?: string | null }
10661+
>;
10662+
})
10663+
) & {
1064010664
description?: string;
10641-
files?: Record<string, (any | any | object | null) & { content?: string; filename?: string | null }>;
10665+
files?: Record<
10666+
string,
10667+
(
10668+
| { content: string }
10669+
| { filename: string | null }
10670+
| object
10671+
| ({ content: string } & { filename: string | null } & object)
10672+
) & { content?: string; filename?: string | null }
10673+
>;
1064210674
},
1064310675
params: RequestParams = {},
1064410676
) =>
@@ -15943,17 +15975,29 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
1594315975
owner: string,
1594415976
repo: string,
1594515977
data: (
15946-
| { status?: "completed"; [key: string]: any }
15947-
| { status?: "queued" | "in_progress"; [key: string]: any }
15948-
| ({ status?: "completed"; [key: string]: any } & { status?: "queued" | "in_progress"; [key: string]: any })
15978+
| {
15979+
status?: "completed";
15980+
conclusion: "success" | "failure" | "neutral" | "cancelled" | "skipped" | "timed_out" | "action_required";
15981+
name: string;
15982+
head_sha: string;
15983+
[key: string]: any;
15984+
}
15985+
| { status?: "queued" | "in_progress"; name: string; head_sha: string; [key: string]: any }
15986+
| ({
15987+
status?: "completed";
15988+
conclusion: "success" | "failure" | "neutral" | "cancelled" | "skipped" | "timed_out" | "action_required";
15989+
name: string;
15990+
head_sha: string;
15991+
[key: string]: any;
15992+
} & { status?: "queued" | "in_progress"; name: string; head_sha: string; [key: string]: any })
1594915993
) & {
15950-
name?: string;
15994+
name: string;
1595115995
head_sha: string;
1595215996
details_url?: string;
1595315997
external_id?: string;
1595415998
status?: "queued" | "in_progress" | "completed";
1595515999
started_at?: string;
15956-
conclusion: "success" | "failure" | "neutral" | "cancelled" | "skipped" | "timed_out" | "action_required";
16000+
conclusion?: "success" | "failure" | "neutral" | "cancelled" | "skipped" | "timed_out" | "action_required";
1595716001
completed_at?: string;
1595816002
output?: {
1595916003
title: string;
@@ -16014,9 +16058,17 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
1601416058
repo: string,
1601516059
checkRunId: number,
1601616060
data: (
16017-
| { status?: "completed"; [key: string]: any }
16061+
| {
16062+
status?: "completed";
16063+
conclusion: "success" | "failure" | "neutral" | "cancelled" | "skipped" | "timed_out" | "action_required";
16064+
[key: string]: any;
16065+
}
1601816066
| { status?: "queued" | "in_progress"; [key: string]: any }
16019-
| ({ status?: "completed"; [key: string]: any } & { status?: "queued" | "in_progress"; [key: string]: any })
16067+
| ({
16068+
status?: "completed";
16069+
conclusion: "success" | "failure" | "neutral" | "cancelled" | "skipped" | "timed_out" | "action_required";
16070+
[key: string]: any;
16071+
} & { status?: "queued" | "in_progress"; [key: string]: any })
1602016072
) & {
1602116073
name?: string;
1602216074
details_url?: string;

0 commit comments

Comments
 (0)