-
Notifications
You must be signed in to change notification settings - Fork 75
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
Generate types for new MFD design #2455
Changes from 18 commits
9c5be60
51d1ed0
fe180d6
d7bbb59
804de8b
6bba312
42f1389
f6f9930
9c1a89d
e58f5d3
f7990c7
23dda1d
adaa2bc
6b5d840
0f7ccb5
7673232
f0d9cc1
514ffbe
7ee574b
8f9fefb
3484daf
efcae26
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,19 +3,27 @@ | |
|
||
import { | ||
InterfaceDeclarationStructure, | ||
OptionalKind, | ||
PropertySignatureStructure, | ||
StructureKind, | ||
TypeAliasDeclarationStructure | ||
} from "ts-morph"; | ||
import { NameType, normalizeName } from "./helpers/nameUtils.js"; | ||
import { isDictionarySchema, isObjectSchema } from "./helpers/schemaHelpers.js"; | ||
import { | ||
isArraySchema, | ||
isDictionarySchema, | ||
isObjectSchema | ||
} from "./helpers/schemaHelpers.js"; | ||
import { | ||
ArraySchema, | ||
ObjectSchema, | ||
Parameter, | ||
Property, | ||
RLCModel, | ||
Schema, | ||
SchemaContext | ||
} from "./interfaces.js"; | ||
import { getMultipartPartTypeName } from "./helpers/nameConstructors.js"; | ||
|
||
/** | ||
* Generates interfaces for ObjectSchemas | ||
|
@@ -40,6 +48,19 @@ export function buildObjectInterfaces( | |
) { | ||
continue; | ||
} | ||
|
||
// FIXME: disabling new multipart generation for modular while we figure out the story | ||
if (objectSchema.isMultipartBody && !model.options?.isModularLibrary) { | ||
objectInterfaces.push( | ||
...buildMultipartPartDefinitions( | ||
objectSchema, | ||
importedModels, | ||
schemaUsage | ||
) | ||
); | ||
continue; | ||
} | ||
|
||
const baseName = getObjectBaseName(objectSchema, schemaUsage); | ||
const interfaceDeclaration = getObjectInterfaceDeclaration( | ||
model, | ||
|
@@ -54,6 +75,66 @@ export function buildObjectInterfaces( | |
return objectInterfaces; | ||
} | ||
|
||
const MULTIPART_FILE_METADATA_PROPERTIES: OptionalKind<PropertySignatureStructure>[] = | ||
[ | ||
{ | ||
name: "filename", | ||
hasQuestionToken: true, | ||
type: "string" | ||
}, | ||
{ | ||
name: "contentType", | ||
hasQuestionToken: true, | ||
type: "string" | ||
} | ||
]; | ||
|
||
function buildMultipartPartDefinitions( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we consider the case for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not currently but we can revisit if this becomes a requirement |
||
schema: ObjectSchema, | ||
importedModels: Set<string>, | ||
schemaUsage: SchemaContext[] | ||
): InterfaceDeclarationStructure[] { | ||
if (!schema.isMultipartBody) { | ||
return []; | ||
} | ||
|
||
// Transform property signatures into individual models | ||
const propertySignatures = getPropertySignatures( | ||
schema.properties ?? {}, | ||
schemaUsage, | ||
importedModels, | ||
{ flattenBinaryArrays: true } | ||
); | ||
|
||
const structures: InterfaceDeclarationStructure[] = []; | ||
|
||
for (const signature of propertySignatures) { | ||
const name = signature.name; | ||
const typeName = getMultipartPartTypeName(schema.name, name); | ||
|
||
const isFileUpload = signature.type?.toString().includes("File") ?? false; | ||
|
||
structures.push({ | ||
kind: StructureKind.Interface, | ||
isExported: true, | ||
name: typeName, | ||
properties: [ | ||
{ | ||
name: "name", | ||
type: name | ||
}, | ||
{ | ||
name: "body", | ||
type: signature.type | ||
}, | ||
...(isFileUpload ? MULTIPART_FILE_METADATA_PROPERTIES : []) | ||
] | ||
}); | ||
} | ||
|
||
return structures; | ||
} | ||
|
||
export function buildObjectAliases( | ||
model: RLCModel, | ||
importedModels: Set<string>, | ||
|
@@ -67,6 +148,30 @@ export function buildObjectAliases( | |
const objectAliases: TypeAliasDeclarationStructure[] = []; | ||
|
||
for (const objectSchema of objectSchemas) { | ||
// FIXME: disabling new multipart generation for modular while we figure out the story | ||
if (objectSchema.isMultipartBody && !model.options?.isModularLibrary) { | ||
const propertySignatures = getPropertySignatures( | ||
objectSchema.properties ?? {}, | ||
schemaUsage, | ||
importedModels, | ||
{ flattenBinaryArrays: true } | ||
); | ||
|
||
const objectTypeNames = propertySignatures.map((sig) => | ||
getMultipartPartTypeName(objectSchema.name, sig.name) | ||
); | ||
|
||
objectAliases.push({ | ||
kind: StructureKind.TypeAlias, | ||
...(objectSchema.description && { | ||
docs: [{ description: objectSchema.description }] | ||
}), | ||
name: objectSchema.typeName!, | ||
type: `FormData | Array<${objectTypeNames.join("|") || "unknown"}>`, | ||
isExported: true | ||
}); | ||
} | ||
|
||
if (objectSchema.alias || objectSchema.outputAlias) { | ||
const description = objectSchema.description; | ||
const modelName = schemaUsage.includes(SchemaContext.Input) | ||
|
@@ -424,10 +529,15 @@ export function getImmediateParentsNames( | |
return [...parents, ...extendFrom]; | ||
} | ||
|
||
interface GetPropertySignatureOptions { | ||
flattenBinaryArrays?: boolean; | ||
} | ||
|
||
function getPropertySignatures( | ||
properties: { [key: string]: Property }, | ||
schemaUsage: SchemaContext[], | ||
importedModels: Set<string> | ||
importedModels: Set<string>, | ||
options: GetPropertySignatureOptions = {} | ||
) { | ||
let validProperties = Object.keys(properties); | ||
const readOnlyFilter = (name: string) => | ||
|
@@ -438,63 +548,82 @@ function getPropertySignatures( | |
getPropertySignature( | ||
{ ...properties[p], name: p }, | ||
schemaUsage, | ||
importedModels | ||
importedModels, | ||
options | ||
) | ||
); | ||
} | ||
|
||
function isBinaryArray(schema: Schema): boolean { | ||
return Boolean( | ||
isArraySchema(schema) && | ||
(schema.items?.typeName?.includes("NodeJS.ReadableStream") || | ||
schema.items?.outputTypeName?.includes("NodeJS.ReadableStream")) | ||
); | ||
} | ||
|
||
/** | ||
* Builds a Typescript property or parameter signature | ||
* @param property - Property or parameter to get the Typescript signature for | ||
* @param schema - Property or parameter to get the Typescript signature for | ||
* @param importedModels - Set to track the models that need to be imported | ||
* @returns a PropertySignatureStructure for the property. | ||
*/ | ||
export function getPropertySignature( | ||
property: Property | Parameter, | ||
schemaUsage: SchemaContext[], | ||
importedModels: Set<string> | ||
importedModels: Set<string>, | ||
options: GetPropertySignatureOptions = {} | ||
): PropertySignatureStructure { | ||
const propertyName = property.name; | ||
const description = property.description; | ||
let schema: Schema; | ||
if (options.flattenBinaryArrays && isBinaryArray(property)) { | ||
schema = { | ||
...((property as ArraySchema).items ?? property), | ||
name: property.name | ||
}; | ||
} else { | ||
schema = property; | ||
} | ||
|
||
const propertyName = schema.name; | ||
const description = schema.description; | ||
let type; | ||
const hasCoreInArray = | ||
property.type === "array" && | ||
(property as any).items && | ||
(property as any).items.fromCore; | ||
schema.type === "array" && | ||
(schema as any).items && | ||
(schema as any).items.fromCore; | ||
const hasCoreInRecord = | ||
property.type === "dictionary" && | ||
(property as any).additionalProperties && | ||
(property as any).additionalProperties.fromCore; | ||
if (hasCoreInArray && property.typeName) { | ||
type = property.typeName; | ||
schema.type === "dictionary" && | ||
(schema as any).additionalProperties && | ||
(schema as any).additionalProperties.fromCore; | ||
if (hasCoreInArray && schema.typeName) { | ||
type = schema.typeName; | ||
importedModels.add( | ||
(property as any).items.typeName ?? (property as any).items.name | ||
(schema as any).items.typeName ?? (schema as any).items.name | ||
); | ||
} else if (hasCoreInRecord && property.typeName) { | ||
type = property.typeName; | ||
} else if (hasCoreInRecord && schema.typeName) { | ||
type = schema.typeName; | ||
importedModels.add( | ||
(property as any).additionalProperties.typeName ?? | ||
(property as any).additionalProperties.name | ||
(schema as any).additionalProperties.typeName ?? | ||
(schema as any).additionalProperties.name | ||
); | ||
} else { | ||
type = | ||
generateForOutput(schemaUsage, property.usage) && property.outputTypeName | ||
? property.outputTypeName | ||
: property.typeName | ||
? property.typeName | ||
: property.type; | ||
if (property.typeName && property.fromCore) { | ||
importedModels.add(property.typeName); | ||
type = property.typeName; | ||
generateForOutput(schemaUsage, schema.usage) && schema.outputTypeName | ||
? schema.outputTypeName | ||
: schema.typeName | ||
? schema.typeName | ||
: schema.type; | ||
if (schema.typeName && schema.fromCore) { | ||
importedModels.add(schema.typeName); | ||
type = schema.typeName; | ||
} | ||
} | ||
|
||
return { | ||
name: propertyName, | ||
...(description && { docs: [{ description }] }), | ||
hasQuestionToken: !property.required, | ||
isReadonly: | ||
generateForOutput(schemaUsage, property.usage) && property.readOnly, | ||
hasQuestionToken: !schema.required, | ||
isReadonly: generateForOutput(schemaUsage, schema.usage) && schema.readOnly, | ||
type, | ||
kind: StructureKind.PropertySignature | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: what RLC version will be generated in modular SDK? I image that new design would align with v2, old design would align with v1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We just generate a normal RLC model in the modular SDK (as if it was a plain old JSON operation) because of this check. This shape is more in line with v1 of the Core package. We can't generate the v2 models here yet as that results in a compile error, since the serializers don't serialize into the array shape.