UNPKG

@autorest/powershell

Version:
574 lines 33.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.tweakModelPlugin = exports.tweakModelV2 = exports.titleToAzureServiceName = exports.HeaderPropertyType = exports.HeaderProperty = void 0; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ const codemodel_1 = require("@autorest/codemodel"); const codegen_1 = require("@azure-tools/codegen"); const linq_1 = require("@azure-tools/linq"); const model_state_1 = require("../utils/model-state"); const extension_base_1 = require("@autorest/extension-base"); exports.HeaderProperty = 'HeaderProperty'; var HeaderPropertyType; (function (HeaderPropertyType) { HeaderPropertyType["Header"] = "Header"; HeaderPropertyType["HeaderAndBody"] = "HeaderAndBody"; })(HeaderPropertyType || (exports.HeaderPropertyType = HeaderPropertyType = {})); // For now, we are not dynamically changing the service-name. Instead, we would figure out a method to change it during the creation of service readme's. function titleToAzureServiceName(title) { const titleCamelCase = (0, codegen_1.pascalCase)((0, codegen_1.deconstruct)(title)).trim(); const serviceName = titleCamelCase // Remove: !StartsWith(Management)AndContains(Management), Client, Azure, Microsoft, APIs, API, REST .replace(/(?!^Management)(?=.*)Management|Client|Azure|Microsoft|APIs|API|REST/g, '') // Remove: EndsWith(ServiceResourceProvider), EndsWith(ResourceProvider), EndsWith(DataPlane), EndsWith(Data) .replace(/ServiceResourceProvider$|ResourceProvider$|DataPlane$|Data$/g, ''); return serviceName || titleCamelCase; } exports.titleToAzureServiceName = titleToAzureServiceName; function dropDuplicatePropertiesInChildSchemas(schema, state, map = new Map()) { var _a, _b; let success = true; for (const parent of (0, linq_1.values)((_a = schema.parents) === null || _a === void 0 ? void 0 : _a.immediate)) { //handle parents first if (!dropDuplicatePropertiesInChildSchemas(parent, state, map)) { return false; } } for (const { key: id, value: property } of (0, linq_1.items)(schema.properties)) { //see if it's in the parent. const pProp = map.get(property.serializedName); if (pProp) { //if the parent prop is the same type as the child prop //we're going to drop the child property. if (pProp.schema.type === property.schema.type) { //if it's an object type, it has to be the exact same schema type too if (pProp.schema.type != codemodel_1.SchemaType.Object || pProp.schema === property.schema) { state.verbose(`Property '${property.serializedName}' in '${schema.language.default.name}' has a property the same as the parent, and is dropping the duplicate.`, {}); if (schema.properties) { delete schema.properties[id]; } } else { const conflict = `Property '${property.serializedName}' in '${schema.language.default.name}' has a conflict with a parent schema (allOf ${(_b = schema.parents) === null || _b === void 0 ? void 0 : _b.immediate.joinWith(each => each.language.default.name)}.`; state.error(conflict, [], {}); success = false; } } } else { map.set(property.serializedName, property); } } return success; } async function tweakModelV2(state) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; const title = (0, codegen_1.pascalCase)((0, codegen_1.fixLeadingNumber)((0, codegen_1.deconstruct)(await state.getValue('title', state.model.info.title)))); state.setValue('title', title); const serviceName = await state.getValue('service-name', titleToAzureServiceName(title)); state.setValue('service-name', serviceName); const model = state.model; const schemas = model.schemas; // xichen: do we need other schema types? const allSchemas = [...(_a = schemas.objects) !== null && _a !== void 0 ? _a : [], ...(_b = schemas.choices) !== null && _b !== void 0 ? _b : [], ...(_c = schemas.sealedChoices) !== null && _c !== void 0 ? _c : []]; model.commands = { operations: new linq_1.Dictionary(), parameters: new linq_1.Dictionary(), }; // we're going to create a schema that represents the distinct sum // of all operation PATH parameters const universalId = new codemodel_1.ObjectSchema(`${serviceName}Identity`, ''); // xichen: Add 'universal-parameter-type' in language.default.uid, so that we can find it later universalId.language.default.uid = 'universal-parameter-type'; universalId.apiVersions = universalId.apiVersions || []; state.model.schemas.objects = state.model.schemas.objects || []; universalId.language.default.uid = 'universal-parameter-type'; state.model.schemas.objects.push(universalId); for (const group of (0, linq_1.values)(model.operationGroups)) { for (const operation of (0, linq_1.values)(group.operations)) { for (const response of (0, linq_1.values)(operation.responses)) { // Mark returned object in response const respSchema = response.schema; if ((respSchema === null || respSchema === void 0 ? void 0 : respSchema.type) === codemodel_1.SchemaType.Object) { respSchema.extensions = respSchema.extensions || {}; respSchema.extensions['is-return-object'] = true; } } for (const param of (0, linq_1.values)(operation.parameters).where(each => { var _a, _b; return ((_b = (_a = each.protocol) === null || _a === void 0 ? void 0 : _a.http) === null || _b === void 0 ? void 0 : _b.in) === codemodel_1.ParameterLocation.Path; })) { const name = param.language.default.name; const hasName = (_d = universalId.properties) === null || _d === void 0 ? void 0 : _d.find((prop) => prop.language.default.name.toLocaleLowerCase() === name.toLocaleLowerCase()); if (!hasName) { if (!universalId.properties) { universalId.properties = []; } const newProp = new codemodel_1.Property(name, param.language.default.description, param.schema); newProp.required = false; newProp.readOnly = false; newProp.serializedName = param.language.default.serializedName; universalId.properties.push(newProp); } } } } if (await state.getValue('azure', false)) { const idScheam = new codemodel_1.Schema('_identity_type_', 'Resource identity path', codemodel_1.SchemaType.String); const idProp = new codemodel_1.Property('id', 'Resource identity path', idScheam); idProp.readOnly = false; idProp.required = false; idProp.language.default.uid = 'universal-parameter:resource identity'; if (!universalId.properties) { universalId.properties = []; } universalId.properties.push(idProp); } // xichen: do nothing in m3 logic. Comment it out // if an operation has a response that has a schema with string/binary we should make the response application/octet-stream // for (const operationGroups of values(model.operationGroups)) { // for (const operation of values(operationGroups.operations)) { // for (const response of values(operation.responses)) { // if ((response as any).schema) { // const respSchema = response as any; // if (respSchema.type === SchemaType.String && respSchema.format === StringFormat.Binary) { // // WHY WAS THIS HERE?! // // response.mimeTypes = [KnownMediaType.Stream]; // } // } // } // } // } // schemas that have parents and implement properties that are in the parent schemas // will have the property dropped in the child schema for (const schema of (0, linq_1.values)(model.schemas.objects)) { if ((0, linq_1.length)((_e = schema.parents) === null || _e === void 0 ? void 0 : _e.immediate) > 0) { if (!dropDuplicatePropertiesInChildSchemas(schema, state)) { throw new Error('Schemas are in conflict.'); } } } if (await state.getValue('use-storage-pipeline', false)) { // we're going to create new models for the reponse headers ? } else { // if an operation has a body parameter with string/binary, we should make the request application/octet-stream // === Header Schemas === // go thru the operations, find responses that have header values, and add a property to the schemas that are returned with those values for (const operationGroups of (0, linq_1.values)(model.operationGroups)) { for (const operation of (0, linq_1.values)(operationGroups.operations)) { for (const response of (0, linq_1.values)(operation.responses)) { // for a given response, find the possible models that can be returned from the service for (const header of (0, linq_1.values)((_f = response.protocol.http) === null || _f === void 0 ? void 0 : _f.headers)) { if (!response.schema) { // no response schema? can we fake one? // service.message{ Channel: Channel.Debug, Text: `${header.key} is in ${operation.details.default.name} but there is no response model` }); continue; } // if the method response has a schema and it's an object, we're going to add our properties to the schema object. // yes, this means that the reponse model may have properties that are undefined if the server doesn't send back the header // and other operations might add other headers that are not the same. // if the method's response is a primitive value (string, boolean, null, number) or an array, we can't modify that type obviously // in which case, we're going to add a header // work with schemas that have objects only. if (response.schema.type === codemodel_1.SchemaType.Object) { const respSchema = (response.schema); const curHeader = header; const headerKey = curHeader.header; respSchema.language.default.hasHeaders = true; const property = (0, linq_1.values)((0, codemodel_1.getAllProperties)(respSchema)).first((each) => each.language.default.name === headerKey); if (!property) { state.message({ Channel: extension_base_1.Channel.Debug, Text: `Adding header property '${headerKey}' to model ${respSchema.language.default.name}` }); // create a property for the header value const newProperty = new codemodel_1.Property(headerKey, curHeader.description || '', curHeader.schema); newProperty.language.default.required = false; // mark it that it's a header-only property newProperty.language.default[exports.HeaderProperty] = HeaderPropertyType.Header; // add it to this model. if (!respSchema.properties) { respSchema.properties = []; } respSchema.properties.push(newProperty); } else { // there is a property with this name already. // was this previously declared as a header only property? if (!property.language.default[exports.HeaderProperty]) { state.message({ Channel: extension_base_1.Channel.Debug, Text: `Property ${headerKey} in model ${respSchema.language.default.name} can also come from the header.` }); // no.. There is duplication between header and body property. Probably because of etags. // tell it to be a header-and-body property. property.language.default[exports.HeaderProperty] = HeaderPropertyType.HeaderAndBody; property.language.default.name = headerKey; } } } } } } } } // remove well-known header parameters from operations and add mark the operation has supporting that feature for (const operationGroups of (0, linq_1.values)(model.operationGroups)) { for (const operation of (0, linq_1.values)(operationGroups.operations)) { // if we have an operation with a body, and content-type is a multipart/formdata // then we should go thru the parameters of the body and look for a string/binary parameters // and remember to add another parameter for the filename of the string/binary const request = (_g = operation.requests) === null || _g === void 0 ? void 0 : _g[0]; (_h = request === null || request === void 0 ? void 0 : request.parameters) === null || _h === void 0 ? void 0 : _h.filter((param) => { var _a, _b; return param.schema.type !== codemodel_1.SchemaType.Object && ((_a = param.protocol.http) === null || _a === void 0 ? void 0 : _a.in) === 'body' && ((_b = param.protocol.http) === null || _b === void 0 ? void 0 : _b.style) === codegen_1.KnownMediaType.Multipart; }).forEach((param) => { for (const prop of (0, linq_1.values)((0, codemodel_1.getAllProperties)(param.schema))) { if (prop.schema.type === codemodel_1.SchemaType.Binary) { prop.language.default.isNamedStream = true; } } }); // move well-known hearder parameters into details, and we can process them in the generator how we please. // operation.details.default.headerparameters = values(operation.parameters).where(p => p.in === ParameterLocation.Header && ['If-Match', 'If-None-Match'].includes(p.name)).toArray(); // remove if-match and if-none-match parameters from the operation itself. // operation.parameters = values(operation.parameters).where(p => !(p.in === ParameterLocation.Header && ['If-Match', 'If-None-Match'].includes(p.name))).toArray(); } } // identify models that are polymorphic in nature for (const schema of allSchemas) { if (schema instanceof codemodel_1.ObjectSchema) { const objSchema = schema; // if this actual type is polymorphic, make sure we know that. // parent class if (objSchema.discriminator) { objSchema.language.default.isPolymorphic = true; if (objSchema.children) { objSchema.language.default.polymorphicChildren = (_j = objSchema.children) === null || _j === void 0 ? void 0 : _j.all; } } // sub class if (objSchema.discriminatorValue) { objSchema.language.default.discriminatorValue = (_k = objSchema.extensions) === null || _k === void 0 ? void 0 : _k['x-ms-discriminator-value']; } } } // identify parameters that are constants for (const group of (0, linq_1.values)(model.operationGroups)) { for (const operation of (0, linq_1.values)(group.operations)) { for (const parameter of (0, linq_1.values)(operation.parameters)) { if (parameter.required) { if (parameter.schema.type === codemodel_1.SchemaType.Choice) { const choiceSchema = parameter.schema; if (choiceSchema.choices.length === 1) { parameter.language.default.constantValue = choiceSchema.choices[0].value; } } else if (parameter.schema.type === codemodel_1.SchemaType.Constant) { const constantSchema = parameter.schema; parameter.language.default.constantValue = constantSchema.value.value; } else if (parameter.schema.type === codemodel_1.SchemaType.SealedChoice) { const sealedChoiceSchema = parameter.schema; if (sealedChoiceSchema.choices.length === 1) { parameter.language.default.constantValue = sealedChoiceSchema.choices[0].value; if (sealedChoiceSchema.language.default.skip !== false) { sealedChoiceSchema.language.default.skip = true; } } } } else { if (parameter.schema.type === codemodel_1.SchemaType.SealedChoice) { const sealedChoiceSchema = parameter.schema; if (sealedChoiceSchema.choices.length === 1) { sealedChoiceSchema.language.default.skip = false; } } } } } } // identify properties that are constants for (const schema of (0, linq_1.values)(schemas.objects)) { for (const property of (0, linq_1.values)(schema.properties)) { if (property === undefined) { continue; } if (property.required) { if (property.schema.type === codemodel_1.SchemaType.Choice) { const choiceSchema = property.schema; if (choiceSchema.choices.length === 1) { // properties with an enum single value are constants // add the constant value property.language.default.constantValue = choiceSchema.choices[0].value; } } else if (property.schema.type === codemodel_1.SchemaType.Constant) { const constantSchema = property.schema; property.language.default.constantValue = constantSchema.value.value; } else if (property.schema.type === codemodel_1.SchemaType.SealedChoice) { const sealedChoiceSchema = property.schema; if (sealedChoiceSchema.choices.length === 1) { property.language.default.constantValue = sealedChoiceSchema.choices[0].value; if (sealedChoiceSchema.language.default.skip !== false) { sealedChoiceSchema.language.default.skip = true; } } } } else { if (property.schema.type === codemodel_1.SchemaType.SealedChoice) { const sealedChoiceSchema = property.schema; if (sealedChoiceSchema.choices.length === 1) { sealedChoiceSchema.language.default.skip = false; } } } } } // xichen: Do we need skip? // const enumsToSkip = new Set<string>(); // // identify properties that are constants // for (const schema of values(model.schemas)) { // for (const property of values(schema.properties)) { // if (property.details.default.required && length(property.schema.enum) === 1) { // // properties with an enum single value are constants // // add the constant value // property.details.default.constantValue = property.schema.enum[0]; // // mark as skip the generation of this model // enumsToSkip.add(property.schema.details.default.uid); // // make it a string and keep its name // property.schema = new Schema(property.schema.details.default.name, { type: property.schema.type }); // } else { // enumsToSkip.delete(property.schema.details.default.uid); // } // } // } // // mark enums that shouldn't be generated // for (const schema of values(model.schemas)) { // if (enumsToSkip.has(schema.details.default.uid)) { // schema.details.default.skip = true; // } // } return model; } exports.tweakModelV2 = tweakModelV2; // async function tweakModel(state: State): Promise<codemodel.Model> { // const title = pascalCase(fixLeadingNumber(deconstruct(await state.getValue('title', state.model.info.title)))); // state.setValue('title', title); // const serviceName = await state.getValue('service-name', titleToAzureServiceName(title)); // state.setValue('service-name', serviceName); // const model = state.model; // model.schemas = model.schemas || []; // const set = new Set<Schema>(); // const removes = new Set<string>(); // for (const key of keys(model.schemas)) { // const value = model.schemas[key]; // if (set.has(value)) { // // this schema is already in the collection. let's drop it when we're done // removes.add(key); // } else { // set.add(value); // } // } // // we're going to create a schema that represents the distinct sum // // of all operation PATH parameters // const universalId = new Schema(`${serviceName}Identity`, { // type: JsonType.Object, description: 'Resource Identity', details: { // default: { // uid: 'universal-parameter-type' // } // } // }); // model.schemas['universal-parameter-type'] = universalId; // for (const operation of values(model.http.operations)) { // for (const param of values(operation.parameters).where(each => each.in === ParameterLocation.Path)) { // const name = param.details.default.name; // if (!universalId.properties[name]) { // universalId.properties[name] = new Property(name, { // schema: param.schema, description: param.description, serializedName: name, details: { // default: { // description: param.description, // name: name, // required: false, // readOnly: false, // uid: `universal-parameter:${name}` // } // } // }); // } // } // } // if (await state.getValue('azure', false)) { // universalId.properties['id'] = new Property('id', { // schema: new Schema('_identity_type_', { type: JsonType.String, description: 'Resource identity path' }), // description: 'Resource identity path', serializedName: 'id', details: { // default: { // description: 'Resource identity path', // name: 'id', // required: false, // readOnly: false, // uid: 'universal-parameter:resource identity' // } // } // }); // } // // remove schemas that are referenced elsewhere previously. // for (const each of removes.values()) { // delete model.schemas[each]; // } // // if an operation has a response that has a schema with string/binary we should make the response application/octet-stream // for (const operation of values(model.http.operations)) { // for (const responses of values(operation.responses)) { // for (const response of responses) { // if (response.schema) { // if (response.schema.type === JsonType.String && response.schema.format === StringFormat.Binary) { // // WHY WAS THIS HERE?! // // response.mimeTypes = [KnownMediaType.Stream]; // } // } // } // } // } // // schemas that have parents and implement properties that are in the parent schemas // // will have the property dropped in the child schema // for (const schema of values(model.schemas)) { // if (length(schema.allOf) > 0) { // if (!dropDuplicatePropertiesInChildSchemas(schema, state)) { // throw new Error('Schemas are in conflict.'); // } // } // } // if (await state.getValue('use-storage-pipeline', false)) { // // we're going to create new models for the reponse headers ? // } else { // // if an operation has a body parameter with string/binary, we should make the request application/octet-stream // // === Header Schemas === // // go thru the operations, find responses that have header values, and add a property to the schemas that are returned with those values // for (const operation of values(model.http.operations)) { // for (const responses of values(operation.responses)) { // for (const response of responses) { // // for a given response, find the possible models that can be returned from the service // for (const header of values(response.headers)) { // if (!response.schema) { // // no response schema? can we fake one? // // service.message{ Channel: Channel.Debug, Text: `${header.key} is in ${operation.details.default.name} but there is no response model` }); // continue; // } // // if the method response has a schema and it's an object, we're going to add our properties to the schema object. // // yes, this means that the reponse model may have properties that are undefined if the server doesn't send back the header // // and other operations might add other headers that are not the same. // // if the method's response is a primitive value (string, boolean, null, number) or an array, we can't modify that type obviously // // in which case, we're going to add a header // // work with schemas that have objects only. // if (isSchemaObject(response.schema)) { // response.schema.details.default.hasHeaders = true; // const property = response.schema.properties[header.key]; // if (!property) { // state.message({ Channel: Channel.Debug, Text: `Adding header property '${header.key}' to model ${response.schema.details.default.name}` }); // // create a property for the header value // const newProperty = new Property(header.key, { schema: header.schema, description: header.description }); // newProperty.details.default.name = header.key; // newProperty.details.default.required = false; // // mark it that it's a header-only property // newProperty.details.default[HeaderProperty] = HeaderPropertyType.Header; // // add it to this model. // response.schema.properties[header.key] = newProperty; // } else { // // there is a property with this name already. // // was this previously declared as a header only property? // if (!property.details.default[HeaderProperty]) { // state.message({ Channel: Channel.Debug, Text: `Property ${header.key} in model ${response.schema.details.default.name} can also come from the header.` }); // // no.. There is duplication between header and body property. Probably because of etags. // // tell it to be a header-and-body property. // property.details.default[HeaderProperty] = HeaderPropertyType.HeaderAndBody; // property.details.default.name = header.key; // } // } // } // } // } // } // } // } // // remove well-known header parameters from operations and add mark the operation has supporting that feature // for (const operation of values(model.http.operations)) { // // if we have an operation with a body, and content-type is a multipart/formdata // // then we should go thru the parameters of the body and look for a string/binary parameters // // and remember to add another parameter for the filename of the string/binary // if (operation.requestBody && knownMediaType(operation.requestBody.contentType) === KnownMediaType.Multipart) { // for (const prop of values(operation.requestBody.schema.properties)) { // if (prop.schema.type === JsonType.String && prop.schema.format === 'binary') { // prop.details.default.isNamedStream = true; // } // } // } // // move well-known hearder parameters into details, and we can process them in the generator how we please. // // operation.details.default.headerparameters = values(operation.parameters).where(p => p.in === ParameterLocation.Header && ['If-Match', 'If-None-Match'].includes(p.name)).toArray(); // // remove if-match and if-none-match parameters from the operation itself. // // operation.parameters = values(operation.parameters).where(p => !(p.in === ParameterLocation.Header && ['If-Match', 'If-None-Match'].includes(p.name))).toArray(); // } // // identify models that are polymorphic in nature // for (const schema of values(model.schemas)) { // // if this actual type is polymorphic, make sure we know that. // if (schema.discriminator) { // schema.details.default.isPolymorphic = true; // } // const parents = getPolymorphicBases(schema); // if (length(parents) > 0) { // // if our parent is polymorphic, then we must have a discriminator value // schema.details.default.discriminatorValue = schema.extensions['x-ms-discriminator-value'] || schema.details.default.name; // // and make sure that all our polymorphic parents have a reference to this type. // for (const parent of getPolymorphicBases(schema)) { // parent.details.default.polymorphicChildren = parent.details.default.polymorphicChildren || new Array<Schema>(); // parent.details.default.polymorphicChildren.push(schema); // } // } // } // // identify parameters that are constants // for (const operation of values(model.http.operations)) { // for (const parameter of values(operation.parameters)) { // if (parameter.required && length(parameter.schema.enum) === 1) { // // parameters with an enum single value are constants // parameter.details.default.constantValue = parameter.schema.enum[0]; // } // } // } // const enumsToSkip = new Set<string>(); // // identify properties that are constants // for (const schema of values(model.schemas)) { // for (const property of values(schema.properties)) { // if (property.details.default.required && length(property.schema.enum) === 1) { // // properties with an enum single value are constants // // add the constant value // property.details.default.constantValue = property.schema.enum[0]; // // mark as skip the generation of this model // enumsToSkip.add(property.schema.details.default.uid); // // make it a string and keep its name // property.schema = new Schema(property.schema.details.default.name, { type: property.schema.type }); // } else { // enumsToSkip.delete(property.schema.details.default.uid); // } // } // } // // mark enums that shouldn't be generated // for (const schema of values(model.schemas)) { // if (enumsToSkip.has(schema.details.default.uid)) { // schema.details.default.skip = true; // } // } // for (const operation of values(model.http.operations)) { // for (const { key: responseCode, value: responses } of items(operation.responses)) { // for (const response of values(responses)) { // if (responseCode === 'default' || response.extensions['x-ms-error-response'] === true) { // response.details.default.isErrorResponse = true; // } // } // } // } // return model; // } // Universal version - // tweaks the code model to adjust things so that the code will generate better. async function tweakModelPlugin(service) { //const session = await startSession<PwshModel>(service, {}, codeModelSchema); const state = await new model_state_1.ModelState(service).init(); //const result = tweakModelV2(session); await service.writeFile({ filename: 'code-model-v4-tweakcodemodel-v2.yaml', content: (0, codegen_1.serialize)(await tweakModelV2(state)), sourceMap: undefined, artifactType: 'code-model-v4' }); //return processCodeModel(tweakModelV2, service, 'tweakcodemodel-v2'); } exports.tweakModelPlugin = tweakModelPlugin; //# sourceMappingURL=plugin-tweak-model.js.map