diff --git a/powershell/cmdlets/class.ts b/powershell/cmdlets/class.ts index 08f0f989406..42a8a6bf3c7 100644 --- a/powershell/cmdlets/class.ts +++ b/powershell/cmdlets/class.ts @@ -182,6 +182,60 @@ export function addDefaultInfo(targetProperty: Property, parameter: any) { export function addInfoAttribute(targetProperty: Property, pType: TypeDeclaration, isRequired: boolean, isReadOnly: boolean, description: string, serializedName: string) { + let pt = pType; + while (pt.elementType) { + switch (pt.elementType.schema.type) { + case JsonType.Object: + if (pt.elementType.schema.details.csharp.interfaceImplementation) { + pt = { + declaration: pt.elementType.schema.details.csharp.interfaceImplementation.declaration, + schema: pt.elementType.schema, + }; + } else { + // arg! it's not done yet. Hope it's not polymorphic itself. + pt = { + declaration: `${pt.elementType.schema.details.csharp.namespace}.${pt.elementType.schema.details.csharp.interfaceName}`, + schema: pt.elementType.schema, + }; + } + break; + + case JsonType.Array: + pt = pt.elementType; + break; + + default: + pt = pt.elementType; + break; + } + } + const ptypes = new Array(); + if (pt.schema && pt.schema && pt.schema.details.csharp.byReference) { + ptypes.push(`typeof(${pt.schema.details.csharp.namespace}.${pt.schema.details.csharp.interfaceName}_Reference)`); + // do we need polymorphic types for by-resource ? Don't think so. + } else { + ptypes.push(`typeof(${pt.declaration})`); + if (pt.schema && pt.schema.details.csharp.classImplementation && pt.schema.details.csharp.classImplementation.discriminators) { + ptypes.push(...[...pt.schema.details.csharp.classImplementation.discriminators.values()].map(each => `typeof(${each.modelInterface.fullName})`)); + } + } + + targetProperty.add(new Attribute(ClientRuntime.InfoAttribute, { + parameters: [ + new LiteralExpression(`\nRequired = ${isRequired}`), + new LiteralExpression(`\nReadOnly = ${isReadOnly}`), + new LiteralExpression(`\nDescription = ${new StringExpression(description).value}`), + new LiteralExpression(`\nSerializedName = ${new StringExpression(serializedName).value}`), + new LiteralExpression(`\nPossibleTypes = new [] { ${ptypes.join(',').replace(/\?/g, '').replace(/undefined\./g, '')} }`), + ] + })); + + +} + + +export function NewAddInfoAttribute(targetProperty: Property, pType: TypeDeclaration, isRequired: boolean, isReadOnly: boolean, description: string, serializedName: string) { + let pt = pType; while (pt.elementType) { switch (pt.elementType.schema.type) { @@ -2403,7 +2457,7 @@ export class NewCmdletClass extends Class { } else { cmdletParameter.add(new Attribute(ParameterAttribute, { parameters: [new LiteralExpression(`Mandatory = ${vParam.required ? 'true' : 'false'}`), new LiteralExpression(`HelpMessage = "${escapeString(desc || '.')}"`)] })); cmdletParameter.add(new Attribute(CategoryAttribute, { parameters: [`${ParameterCategory}.Body`] })); - addInfoAttribute(cmdletParameter, propertyType, !!vParam.required, false, desc, (vParam.origin).property.serializedName); + NewAddInfoAttribute(cmdletParameter, propertyType, !!vParam.required, false, desc, (vParam.origin).property.serializedName); NewAddCompleterInfo(cmdletParameter, vParam); addDefaultInfo(cmdletParameter, vParam); } @@ -2522,7 +2576,7 @@ export class NewCmdletClass extends Class { regularCmdletParameter.add(new Attribute(AllowEmptyCollectionAttribute)); } - addInfoAttribute(regularCmdletParameter, propertyType, vParam.required, false, vParam.description, vParam.origin.name); + NewAddInfoAttribute(regularCmdletParameter, propertyType, vParam.required, false, vParam.description, vParam.origin.name); NewAddCompleterInfo(regularCmdletParameter, vParam); addDefaultInfo(regularCmdletParameter, vParam); @@ -2644,7 +2698,7 @@ export class NewCmdletClass extends Class { shouldAddPassThru = shouldAddPassThru || values(operation.callGraph) .selectMany(httpOperation => values(httpOperation.responses)) //.selectMany(responsesItem => responsesItem.value) - .any(value => value instanceof SchemaResponse); + .any(value => (value).schema === undefined); if (outputTypes.size === 0) { outputTypes.add(`typeof(${dotnet.Bool})`); } diff --git a/powershell/llcsharp/model/model-class-json.ts b/powershell/llcsharp/model/model-class-json.ts index 2e238ddeae5..0bb3ebc8205 100644 --- a/powershell/llcsharp/model/model-class-json.ts +++ b/powershell/llcsharp/model/model-class-json.ts @@ -320,12 +320,12 @@ export class NewJsonSerializableClass extends Class { pushTempVar(); for (const prop of values(modelClass.ownedProperties)) { - if (prop.details.csharp.HeaderProperty === 'Header') { + if (prop.language.csharp.HeaderProperty === 'Header') { continue; } const serializeStatement = (prop.type).serializeToContainerMember(KnownMediaType.Json, prop.valuePrivate, container, prop.serializedName, mode); - if (prop.details.csharp.readOnly) { + if (prop.language.csharp.readOnly) { serializeStatements.add(If(`${mode.use}.HasFlag(${ClientRuntime.SerializationMode.IncludeReadOnly})`, serializeStatement)); } else { serializeStatements.add(serializeStatement); diff --git a/powershell/llcsharp/model/model-class.ts b/powershell/llcsharp/model/model-class.ts index b3f613542c1..904c040605b 100644 --- a/powershell/llcsharp/model/model-class.ts +++ b/powershell/llcsharp/model/model-class.ts @@ -13,11 +13,13 @@ import { EnhancedTypeDeclaration, NewEnhancedTypeDeclaration } from '../schema/e import { ObjectImplementation, NewObjectImplementation } from '../schema/object'; import { ModelInterface, NewModelInterface } from './interface'; import { JsonSerializableClass, NewJsonSerializableClass } from './model-class-json'; -import { ModelProperty } from './property'; +import { ModelProperty, NewModelProperty } from './property'; import { PropertyOriginAttribute, DoNotFormatAttribute, FormatTableAttribute } from '../csharp-declarations'; import { Schema } from '../code-model'; import { DictionaryImplementation } from './model-class-dictionary'; -import { Languages, Language } from '@azure-tools/codemodel'; +import { Languages, Language, Schema as NewSchema } from '@azure-tools/codemodel'; + +import { VirtualProperty as NewVirtualProperty } from '../../utils/schema'; export function getVirtualPropertyName(vp?: VirtualProperty): string { @@ -26,6 +28,15 @@ export function getVirtualPropertyName(vp?: VirtualProperty): string { } return vp ? vp.name : ''; } + +export function NewGetVirtualPropertyName(vp?: NewVirtualProperty): string { + + if (vp && vp.accessViaMember && vp.accessViaProperty?.accessViaMember) { + return NewGetVirtualPropertyName(vp.accessViaMember); + } + return vp ? vp.name : ''; +} + export interface BackingField { field: Field; typeDeclaration: TypeDeclaration; @@ -556,8 +567,8 @@ export class NewModelClass extends Class implements NewEnhancedTypeDeclaration { /* @internal */ dictionaryImpl?: DictionaryImplementation; private readonly validationStatements = new Statements(); - public ownedProperties = new Array(); - private pMap = new Map(); + public ownedProperties = new Array(); + private pMap = new Map(); // public hasHeaderProperties: boolean; @@ -623,28 +634,28 @@ export class NewModelClass extends Class implements NewEnhancedTypeDeclaration { } } - private nested(virtualProperty: VirtualProperty, internal: boolean): string { + private nested(virtualProperty: NewVirtualProperty, internal: boolean): string { if (virtualProperty.accessViaProperty) { if (virtualProperty.accessViaProperty.accessViaProperty) { // return `/*1*/${getVirtualPropertyName(virtualProperty.accessViaMember)}.${this.nested(virtualProperty.accessViaProperty.accessViaProperty, internal)}`; - return `${getVirtualPropertyName(virtualProperty.accessViaMember)}.${this.nested(virtualProperty.accessViaProperty.accessViaProperty, internal)}`; + return `${NewGetVirtualPropertyName(virtualProperty.accessViaMember)}.${this.nested(virtualProperty.accessViaProperty.accessViaProperty, internal)}`; } } //return `/*2*/${getVirtualPropertyName(virtualProperty.accessViaMember)}`; - return `${getVirtualPropertyName(virtualProperty.accessViaMember)}`; + return `${NewGetVirtualPropertyName(virtualProperty.accessViaMember)}`; } - private accessor(virtualProperty: VirtualProperty, internal = false): string { + private accessor(virtualProperty: NewVirtualProperty, internal = false): string { if (virtualProperty.accessViaProperty) { const prefix = virtualProperty.accessViaProperty.accessViaProperty ? this.nested(virtualProperty.accessViaProperty.accessViaProperty, internal) : ''; const containingProperty = this.pMap.get(virtualProperty.accessViaProperty); if (containingProperty && virtualProperty.accessViaMember) { //return `/*3*/((${virtualProperty.accessViaMember.originalContainingSchema.details.csharp.fullInternalInterfaceName})${containingProperty.name}${prefix}).${getVirtualPropertyName(virtualProperty.accessViaMember)}`; - return `((${virtualProperty.accessViaMember.originalContainingSchema.details.csharp.fullInternalInterfaceName})${containingProperty.name}${prefix}).${getVirtualPropertyName(virtualProperty.accessViaMember)}`; + return `((${virtualProperty.accessViaMember.originalContainingSchema.language.csharp?.fullInternalInterfaceName})${containingProperty.name}${prefix}).${NewGetVirtualPropertyName(virtualProperty.accessViaMember)}`; } } // return `/*4* ${virtualProperty.name}/${virtualProperty.accessViaMember?.name}/${virtualProperty.accessViaProperty?.name} */${getVirtualPropertyName(virtualProperty.accessViaMember) || '/*!!*/' + virtualProperty.name}`; - return `${getVirtualPropertyName(virtualProperty.accessViaMember) || virtualProperty.name}`; + return `${NewGetVirtualPropertyName(virtualProperty.accessViaMember) || virtualProperty.name}`; } private createProperties() { @@ -653,7 +664,7 @@ export class NewModelClass extends Class implements NewEnhancedTypeDeclaration { // add properties if (this.schema.language.csharp?.virtualProperties) { - const addFormatAttributesToProperty = (property: Property, virtualProperty: VirtualProperty) => { + const addFormatAttributesToProperty = (property: Property, virtualProperty: NewVirtualProperty) => { if (virtualProperty.format) { if (virtualProperty.format.suppressFormat) { property.add(new Attribute(DoNotFormatAttribute)); @@ -676,138 +687,138 @@ export class NewModelClass extends Class implements NewEnhancedTypeDeclaration { } }; // skip-for-time-being - // /* Owned Properties */ - // for (const virtualProperty of values(this.schema.language.csharp.virtualProperties.owned)) { - // const actualProperty = virtualProperty.property; - // let n = 0; - // const decl = this.state.project.modelsNamespace.resolveTypeDeclaration(actualProperty.schema, actualProperty.details.csharp.required, this.state.path('schema')); - - // /* public property */ - // const myProperty = new ModelProperty(virtualProperty.name, actualProperty.schema, actualProperty.details.csharp.required, actualProperty.serializedName, actualProperty.details.csharp.description, this.state.path('properties', n++), { - // initializer: actualProperty.details.csharp.constantValue ? typeof actualProperty.details.csharp.constantValue === 'string' ? new StringExpression(actualProperty.details.csharp.constantValue) : new LiteralExpression(actualProperty.details.csharp.constantValue) : undefined - // }); - - // if (actualProperty.details.csharp.readOnly) { - // myProperty.set = undefined; - // } - // myProperty.details = virtualProperty.property.details; - - // if (actualProperty.details.csharp.constantValue !== undefined) { - // myProperty.setAccess = Access.Internal; - // myProperty.set = undefined; - // } - - // if (virtualProperty.private) { - // // when properties are inlined, the container accessor can be internalized. I think. - // myProperty.setAccess = Access.Internal; - // myProperty.getAccess = Access.Internal; - // this.pMap.set(virtualProperty, myProperty); - // } - - // this.ownedProperties.push(this.add(myProperty)); - - // if (myProperty.getAccess !== Access.Public || myProperty.setAccess !== Access.Public || myProperty.set === undefined) { - // /* internal interface property */ - - // this.add(new Property(`${virtualProperty.originalContainingSchema.details.csharp.internalInterfaceImplementation.fullName}.${virtualProperty.name}`, decl, { - // description: `Internal Acessors for ${virtualProperty.name}`, - // getAccess: Access.Explicit, - // setAccess: Access.Explicit, - // get: myProperty.get, - // set: myProperty.assignPrivate('value') - // })); - // } - - // if (this.state.getValue('powershell')) { - // myProperty.add(new Attribute(PropertyOriginAttribute, { parameters: [`${this.state.project.serviceNamespace}.PropertyOrigin.Owned`] })); - // addFormatAttributesToProperty(myProperty, virtualProperty); - // } - // } - - // /* Inherited properties. */ - // for (const virtualProperty of values(this.schema.details.csharp.virtualProperties.inherited)) { - // // so each parent property that is getting exposed - // // has to be accessed via the field in this.backingFields - // const parentField = this.backingFields.find(each => virtualProperty.accessViaSchema ? virtualProperty.accessViaSchema.details.csharp.interfaceImplementation.fullName === each.typeDeclaration.declaration : false); - - // const propertyType = this.state.project.modelsNamespace.resolveTypeDeclaration(virtualProperty.property.schema, virtualProperty.property.details.csharp.required, this.state); - // const opsType = this.state.project.modelsNamespace.resolveTypeDeclaration(virtualProperty.originalContainingSchema, false, this.state); - // const via = virtualProperty.accessViaProperty; - // const parentCast = `(${virtualProperty.originalContainingSchema.details.csharp.internalInterfaceImplementation.fullName})`; - // const vp = this.add(new Property(virtualProperty.name, propertyType, { - // description: virtualProperty.property.details.csharp.description, - // get: toExpression(`(${parentCast}${parentField.field.name}).${this.accessor(virtualProperty)}`), - // set: (virtualProperty.property.details.csharp.readOnly || virtualProperty.property.details.csharp.constantValue) ? undefined : toExpression(`(${parentCast}${parentField.field.name}).${this.accessor(virtualProperty)} = value`) - // })); - - // if (virtualProperty.property.details.csharp.constantValue !== undefined) { - // vp.setAccess = Access.Internal; - // vp.set = undefined; - // } - - // if (vp.getAccess !== Access.Public || vp.setAccess !== Access.Public || vp.set === undefined) { - - // this.add(new Property(`${virtualProperty.originalContainingSchema.details.csharp.internalInterfaceImplementation.fullName}.${virtualProperty.name}`, propertyType, { - // description: `Internal Acessors for ${virtualProperty.name}`, - // getAccess: Access.Explicit, - // setAccess: Access.Explicit, - // get: toExpression(`(${parentCast}${parentField.field.name}).${via.name}`), - // set: toExpression(`(${parentCast}${parentField.field.name}).${via.name} = value`) - // })); - // } - - // if (this.state.getValue('powershell')) { - // vp.add(new Attribute(PropertyOriginAttribute, { parameters: [`${this.state.project.serviceNamespace}.PropertyOrigin.Inherited`] })); - // addFormatAttributesToProperty(vp, virtualProperty); - // } - // } - - // /* Inlined properties. */ - // for (const virtualProperty of values(this.schema.details.csharp.virtualProperties.inlined)) { - // if (virtualProperty.private) { - // // continue; - // // can't remove it, it has to be either public or internally implemented. - // } - - // if (virtualProperty.accessViaProperty) { - // const containingProperty = this.pMap.get(virtualProperty.accessViaProperty); - // if (containingProperty) { - - // const propertyType = this.state.project.modelsNamespace.resolveTypeDeclaration(virtualProperty.property.schema, virtualProperty.property.details.csharp.required, this.state); - - // // regular inlined property - // const vp = new Property(virtualProperty.name, propertyType, { - // description: virtualProperty.property.details.csharp.description, - // get: toExpression(`${this.accessor(virtualProperty)}`), - // set: (virtualProperty.property.details.csharp.readOnly || virtualProperty.property.details.csharp.constantValue) ? undefined : toExpression(`${this.accessor(virtualProperty)} = value`) - // }); - - // if (!virtualProperty.private) { - // this.add(vp); - // } - - // if (virtualProperty.private || vp.getAccess !== Access.Public || vp.setAccess !== Access.Public || vp.set === undefined) { - // this.add(new Property(`${virtualProperty.originalContainingSchema.details.csharp.internalInterfaceImplementation.fullName}.${virtualProperty.name}`, propertyType, { - // description: `Internal Acessors for ${virtualProperty.name}`, - // getAccess: Access.Explicit, - // setAccess: Access.Explicit, - // get: vp.get, - // set: toExpression(`${this.accessor(virtualProperty)} = value`) - // })); - // } - - // if (virtualProperty.property.details.csharp.constantValue !== undefined) { - // vp.setAccess = Access.Internal; - // vp.set = undefined; - // } - - // if (this.state.getValue('powershell')) { - // vp.add(new Attribute(PropertyOriginAttribute, { parameters: [`${this.state.project.serviceNamespace}.PropertyOrigin.Inlined`] })); - // addFormatAttributesToProperty(vp, virtualProperty); - // } - // } - // } - // } + /* Owned Properties */ + for (const virtualProperty of values(>(this.schema.language.csharp.virtualProperties.owned))) { + const actualProperty = virtualProperty.property; + let n = 0; + const decl = this.state.project.modelsNamespace.NewResolveTypeDeclaration(actualProperty.schema, actualProperty.language.csharp?.required, this.state.path('schema')); + + /* public property */ + const myProperty = new NewModelProperty(virtualProperty.name, actualProperty.schema, actualProperty.language.csharp?.required, actualProperty.serializedName, actualProperty.language.csharp?.description || '', this.state.path('properties', n++), { + initializer: actualProperty.language.csharp?.constantValue ? typeof actualProperty.language.csharp.constantValue === 'string' ? new StringExpression(actualProperty.language.csharp.constantValue) : new LiteralExpression(actualProperty.language.csharp.constantValue) : undefined + }); + + if (actualProperty.language.csharp?.readOnly) { + myProperty.set = undefined; + } + myProperty.language = virtualProperty.property.language; + + if (actualProperty.language.csharp?.constantValue !== undefined) { + myProperty.setAccess = Access.Internal; + myProperty.set = undefined; + } + + if (virtualProperty.private) { + // when properties are inlined, the container accessor can be internalized. I think. + myProperty.setAccess = Access.Internal; + myProperty.getAccess = Access.Internal; + this.pMap.set(virtualProperty, myProperty); + } + + this.ownedProperties.push(this.add(myProperty)); + + if (myProperty.getAccess !== Access.Public || myProperty.setAccess !== Access.Public || myProperty.set === undefined) { + /* internal interface property */ + + this.add(new Property(`${virtualProperty.originalContainingSchema.language.csharp?.internalInterfaceImplementation.fullName}.${virtualProperty.name}`, decl, { + description: `Internal Acessors for ${virtualProperty.name}`, + getAccess: Access.Explicit, + setAccess: Access.Explicit, + get: myProperty.get, + set: myProperty.assignPrivate('value') + })); + } + + if (this.state.getValue('powershell')) { + myProperty.add(new Attribute(PropertyOriginAttribute, { parameters: [`${this.state.project.serviceNamespace}.PropertyOrigin.Owned`] })); + addFormatAttributesToProperty(myProperty, virtualProperty); + } + } + + /* Inherited properties. */ + for (const virtualProperty of values(>(this.schema.language.csharp.virtualProperties.inherited))) { + // so each parent property that is getting exposed + // has to be accessed via the field in this.backingFields + const parentField = this.backingFields.find(each => virtualProperty.accessViaSchema ? virtualProperty.accessViaSchema.language.csharp?.interfaceImplementation.fullName === each.typeDeclaration.declaration : false); + + const propertyType = this.state.project.modelsNamespace.NewResolveTypeDeclaration(virtualProperty.property.schema, virtualProperty.property.language.csharp?.required, this.state); + const opsType = this.state.project.modelsNamespace.NewResolveTypeDeclaration(virtualProperty.originalContainingSchema, false, this.state); + const via = virtualProperty.accessViaProperty; + const parentCast = `(${virtualProperty.originalContainingSchema.language.csharp?.internalInterfaceImplementation.fullName})`; + const vp = this.add(new Property(virtualProperty.name, propertyType, { + description: virtualProperty.property.language.csharp?.description, + get: toExpression(`(${parentCast}${parentField.field.name}).${this.accessor(virtualProperty)}`), + set: (virtualProperty.property.language.csharp?.readOnly || virtualProperty.property.language.csharp?.constantValue) ? undefined : toExpression(`(${parentCast}${parentField.field.name}).${this.accessor(virtualProperty)} = value`) + })); + + if (virtualProperty.property.language.csharp?.constantValue !== undefined) { + vp.setAccess = Access.Internal; + vp.set = undefined; + } + + if (vp.getAccess !== Access.Public || vp.setAccess !== Access.Public || vp.set === undefined) { + + this.add(new Property(`${virtualProperty.originalContainingSchema.language.csharp?.internalInterfaceImplementation.fullName}.${virtualProperty.name}`, propertyType, { + description: `Internal Acessors for ${virtualProperty.name}`, + getAccess: Access.Explicit, + setAccess: Access.Explicit, + get: toExpression(`(${parentCast}${parentField.field.name}).${via.name}`), + set: toExpression(`(${parentCast}${parentField.field.name}).${via.name} = value`) + })); + } + + if (this.state.getValue('powershell')) { + vp.add(new Attribute(PropertyOriginAttribute, { parameters: [`${this.state.project.serviceNamespace}.PropertyOrigin.Inherited`] })); + addFormatAttributesToProperty(vp, virtualProperty); + } + } + + /* Inlined properties. */ + for (const virtualProperty of values(>this.schema.language.csharp.virtualProperties.inlined)) { + if (virtualProperty.private) { + // continue; + // can't remove it, it has to be either public or internally implemented. + } + + if (virtualProperty.accessViaProperty) { + const containingProperty = this.pMap.get(virtualProperty.accessViaProperty); + if (containingProperty) { + + const propertyType = this.state.project.modelsNamespace.NewResolveTypeDeclaration(virtualProperty.property.schema, virtualProperty.property.language.csharp?.required, this.state); + + // regular inlined property + const vp = new Property(virtualProperty.name, propertyType, { + description: virtualProperty.property.language.csharp?.description, + get: toExpression(`${this.accessor(virtualProperty)}`), + set: (virtualProperty.property.language.csharp?.readOnly || virtualProperty.property.language.csharp?.constantValue) ? undefined : toExpression(`${this.accessor(virtualProperty)} = value`) + }); + + if (!virtualProperty.private) { + this.add(vp); + } + + if (virtualProperty.private || vp.getAccess !== Access.Public || vp.setAccess !== Access.Public || vp.set === undefined) { + this.add(new Property(`${virtualProperty.originalContainingSchema.language.csharp?.internalInterfaceImplementation.fullName}.${virtualProperty.name}`, propertyType, { + description: `Internal Acessors for ${virtualProperty.name}`, + getAccess: Access.Explicit, + setAccess: Access.Explicit, + get: vp.get, + set: toExpression(`${this.accessor(virtualProperty)} = value`) + })); + } + + if (virtualProperty.property.language.csharp?.constantValue !== undefined) { + vp.setAccess = Access.Internal; + vp.set = undefined; + } + + if (this.state.getValue('powershell')) { + vp.add(new Attribute(PropertyOriginAttribute, { parameters: [`${this.state.project.serviceNamespace}.PropertyOrigin.Inlined`] })); + addFormatAttributesToProperty(vp, virtualProperty); + } + } + } + } } } diff --git a/powershell/llcsharp/model/property.ts b/powershell/llcsharp/model/property.ts index d846cd77f11..dd21d43c46e 100644 --- a/powershell/llcsharp/model/property.ts +++ b/powershell/llcsharp/model/property.ts @@ -11,12 +11,13 @@ import { OneOrMoreStatements } from '@azure-tools/codegen-csharp'; import { Variable } from '@azure-tools/codegen-csharp'; import { Property, Schema } from '../code-model'; import { EnhancedVariable } from '../extended-variable'; -import { EnhancedTypeDeclaration } from '../schema/extended-type-declaration'; +import { EnhancedTypeDeclaration, NewEnhancedTypeDeclaration } from '../schema/extended-type-declaration'; -import { State } from '../generator'; +import { State, NewState } from '../generator'; import { ModelClass } from './model-class'; import { DeepPartial } from '@azure-tools/codegen'; +import { Schema as NewSchema, SchemaType } from '@azure-tools/codemodel'; export class ModelProperty extends BackedProperty implements EnhancedVariable { /** emits an expression to deserialize a property from a member inside a container */ @@ -81,3 +82,68 @@ export class ModelProperty extends BackedProperty implements EnhancedVariable { } + +export class NewModelProperty extends BackedProperty implements EnhancedVariable { + /** emits an expression to deserialize a property from a member inside a container */ + deserializeFromContainerMember(mediaType: KnownMediaType, container: ExpressionOrLiteral, serializedName: string): Expression { + return this.typeDeclaration.deserializeFromContainerMember(mediaType, container, serializedName, this); + } + + /** emits an expression to deserialze a container as the value itself. */ + deserializeFromNode(mediaType: KnownMediaType, node: ExpressionOrLiteral): Expression { + return this.typeDeclaration.deserializeFromNode(mediaType, node, this); + } + + /** emits an expression serialize this to the value required by the container */ + serializeToNode(mediaType: KnownMediaType, serializedName: string, mode: Expression): Expression { + return this.typeDeclaration.serializeToNode(mediaType, this, serializedName, mode); + } + + /** emits an expression serialize this to a HttpContent */ + serializeToContent(mediaType: KnownMediaType, mode: Expression): Expression { + return this.typeDeclaration.serializeToContent(mediaType, this, mode); + } + + /** emits the code required to serialize this into a container */ + serializeToContainerMember(mediaType: KnownMediaType, container: Variable, serializedName: string, mode: Expression): OneOrMoreStatements { + return this.typeDeclaration.serializeToContainerMember(mediaType, container, this, serializedName, mode); + } + public validatePresenceStatement(eventListener: Variable): OneOrMoreStatements { + if (this.required) { + return (this.type).validatePresence(eventListener, this); + } + return ''; + } + public validationStatement(eventListener: Variable): OneOrMoreStatements { + return (this.type).validateValue(eventListener, this); + } + + private required: boolean; + // DISABLED + // public IsHeaderProperty: boolean; + public schema: NewSchema; + public serializedName: string; + private typeDeclaration: NewEnhancedTypeDeclaration; + public language: any; + + constructor(name: string, schema: NewSchema, isRequired: boolean, serializedName: string, description: string, state: NewState, objectInitializer?: DeepPartial) { + const decl = state.project.modelsNamespace.NewResolveTypeDeclaration(schema, isRequired, state.path('schema')); + super(name, decl); + this.typeDeclaration = decl; + this.serializedName = serializedName; + this.schema = schema; + // skip-for-time-being + // if (this.schema.readOnly) { + // this.set = undefined; + // } + this.apply(objectInitializer); + this.description = description; + this.required = isRequired; + if (this.schema.type === SchemaType.Object && isAnExpression(this.get) && schema.language.csharp?.classImplementation) { + // for objects, the getter should auto-create a new object + this.get = toExpression(`(${this.get.value} = ${this.get.value} ?? new ${schema.language.csharp?.fullname}())`); + } + } + + +} \ No newline at end of file diff --git a/powershell/plugins/cs-namer-v2.ts b/powershell/plugins/cs-namer-v2.ts index 898178d4ca1..c39262b7b4f 100644 --- a/powershell/plugins/cs-namer-v2.ts +++ b/powershell/plugins/cs-namer-v2.ts @@ -21,29 +21,32 @@ type State = NewModelState; function setPropertyNames(schema: Schema) { // name each property in this schema // skip-for-time-being - // for (const propertySchema of values(schema.properties)) { - // const propertyDetails = propertySchema.details.default; + if (!isObjectSchema(schema)) { + return; + } + for (const propertySchema of values(schema.properties)) { + const propertyDetails = propertySchema.language.default; - // const className = schema.details.csharp.name; + const className = schema.language.csharp?.name; - // let pname = getPascalIdentifier(propertyDetails.name); - // if (pname === className) { - // pname = `${pname}Property`; - // } + let pname = getPascalIdentifier(propertyDetails.name); + if (pname === className) { + pname = `${pname}Property`; + } - // if (pname === 'default') { - // pname = '@default'; - // } + if (pname === 'default') { + pname = '@default'; + } - // propertySchema.details.csharp = { - // ...propertyDetails, - // name: pname // and so are the propertyNmaes - // }; + propertySchema.language.csharp = { + ...propertyDetails, + name: pname // and so are the propertyNmaes + }; - // if (propertyDetails.isNamedStream) { - // propertySchema.details.csharp.namedStreamPropertyName = pascalCase(fixLeadingNumber([...deconstruct(propertyDetails.name), 'filename'])); - // } - // } + if (propertyDetails.isNamedStream) { + propertySchema.language.csharp.namedStreamPropertyName = pascalCase(fixLeadingNumber([...deconstruct(propertyDetails.name), 'filename'])); + } + } } diff --git a/powershell/plugins/plugin-create-inline-properties.ts b/powershell/plugins/plugin-create-inline-properties.ts index 1bd46a69f02..90cd2f1bd7b 100644 --- a/powershell/plugins/plugin-create-inline-properties.ts +++ b/powershell/plugins/plugin-create-inline-properties.ts @@ -13,6 +13,7 @@ import { CommandOperation } from '../utils/command-operation'; import { PwshModel } from '../utils/PwshModel'; import { NewModelState } from '../utils/model-state'; import { VirtualParameter } from '../utils/command-operation'; +import { VirtualProperty, getAllProperties, getAllPublicVirtualProperties } from '../utils/schema'; function getPluralizationService(): EnglishPluralizationService { const result = new EnglishPluralizationService(); @@ -42,248 +43,254 @@ function getNameOptions(typeName: string, components: Array) { } -// function createVirtualProperties(schema: Schema, stack: Array, threshold: number, conflicts: Array) { -// // dolauli -// // owned: all properties(obj & nonobj) in the schema, -// // inherited: Properties from parents, -// // inlined: for obj properties, flatten them to children, -// // did we already inline this object -// if (schema.details.default.inline === 'yes') { -// return true; -// } - -// if (schema.details.default.inline === 'no') { -// return false; -// } - -// // this is bad. This would happen when we have a circular reference in the tree. -// // dolauli curious in which case this will happen, got it to use no-inline to skip inline and avoid circular reference -// if (schema.details.default.inline === 'inprogress') { -// let text = (`Note: during processing of '${schema.details.default.name}' a circular reference has been discovered.`); -// text += '\n In order to proceed, you must add a directive to indicate which model you want to not inline.\n'; -// text += '\ndirective:'; -// text += '\n- no-inline: # choose ONE of these models to disable inlining'; -// for (const each of stack) { -// text += (`\n - ${each} `); -// } -// text += '\n'; -// conflicts.push(text); -// /* `directive: -// - no-inline: -// - MyModel -// - YourModel -// - HerModel -// ` */ - -// // `, and we're skipping inlining.\n ${stack.join(' => ')}`); -// // mark it as 'not-inlining' -// schema.details.default.inline = 'no'; -// return false; -// } - -// // ok, set to in progress now. -// schema.details.default.inline = 'inprogress'; - -// // virutual property set. -// const virtualProperties = schema.details.default.virtualProperties = { -// owned: new Array(), -// inherited: new Array(), -// inlined: new Array(), -// }; - -// // First we should run thru the properties in parent classes and create inliners for each property they have. -// // dolauli handle properties in parents -// for (const parentSchema of values(schema.allOf)) { -// // make sure that the parent is done. -// createVirtualProperties(parentSchema, [...stack, `${schema.details.default.name}`], threshold, conflicts); - -// const parentProperties = parentSchema.details.default.virtualProperties || { -// owned: [], -// inherited: [], -// inlined: [], -// }; - -// // now we go thru the parent's virutal properties and create our own copies -// for (const virtualProperty of [...parentProperties.inherited, ...parentProperties.inlined, ...parentProperties.owned]) { -// // make sure that we have a list of shared owners of this property. -// virtualProperty.sharedWith = virtualProperty.sharedWith || [virtualProperty]; - -// // we are just copying over theirs to ours. -// const inheritedProperty = { -// name: virtualProperty.name, -// property: virtualProperty.property, -// private: virtualProperty.private, -// nameComponents: virtualProperty.nameComponents, -// nameOptions: virtualProperty.nameOptions, -// accessViaProperty: virtualProperty, -// accessViaMember: virtualProperty, -// accessViaSchema: parentSchema, -// originalContainingSchema: virtualProperty.originalContainingSchema, -// description: virtualProperty.description, -// alias: [], -// required: virtualProperty.required || !!values(schema.required).first(each => !!each && !!each.toLowerCase && each.toLowerCase() === virtualProperty.property.details.default.name.toLowerCase()), -// sharedWith: virtualProperty.sharedWith, -// }; -// // add it to the list of virtual properties that share this property. -// virtualProperty.sharedWith.push(inheritedProperty); - -// // add it to this class. -// virtualProperties.inherited.push(inheritedProperty); -// } -// } - -// // dolauli figure out object properties and non object properties in this class -// const [objectProperties, nonObjectProperties] = values(schema.properties).bifurcate(each => -// !schema.details.default['skip-inline'] && // if this schema is marked skip-inline, none can be inlined, treat them all as straight properties. -// !each.schema.details.default['skip-inline'] && // if the property schema is marked skip-inline, then it should not be processed either. -// each.schema.type === JsonType.Object && // is it an object -// getAllProperties(each.schema).length > 0 // does it have properties (or inherit properties) -// ); - -// // run thru the properties in this class. -// // dolauli handle properties in this class -// for (const property of objectProperties) { -// const propertyName = property.details.default.name; - -// // for each object member, make sure that it's inlined it's children that it can. -// createVirtualProperties(property.schema, [...stack, `${schema.details.default.name}`], threshold, conflicts); - -// // this happens if there is a circular reference. -// // this means that this class should not attempt any inlining of that property at all . -// // dolauli pay attention to the condition check -// const canInline = -// // (!property.schema.details.default['skip-inline']) && -// (!property.schema.details.default.byReference) && -// (!property.schema.additionalProperties) && -// property.schema.details.default.inline === 'yes'; - -// // the target has properties that we can inline -// const virtualChildProperties = property.schema.details.default.virtualProperties || { -// owned: [], -// inherited: [], -// inlined: [], -// }; - -// const allNotRequired = values(getAllPublicVirtualProperties()).all(each => !each.property.details.default.required); - -// const childCount = length(virtualChildProperties.owned) + length(virtualChildProperties.inherited) + length(virtualChildProperties.inlined); -// if (canInline && (property.details.default.required || allNotRequired) && (childCount < threshold || propertyName === 'properties')) { - - -// // if the child property is low enough (or it's 'properties'), let's create virtual properties for each one. -// // create a private property for the inlined ones to use. -// const privateProperty = { -// name: getPascalIdentifier(propertyName), -// propertySchema: schema, -// property, -// nameComponents: [getPascalIdentifier(propertyName)], -// nameOptions: getNameOptions(schema.details.default.name, [propertyName]), -// private: true, -// description: property.description || '', -// originalContainingSchema: schema, -// alias: [], -// required: property.details.default.required, -// }; -// virtualProperties.owned.push(privateProperty); - -// for (const inlinedProperty of [...virtualChildProperties.inherited, ...virtualChildProperties.owned]) { -// // child properties are be inlined without prefixing the name with the property name -// // unless there is a collision, in which case, we have to resolve - -// // (scan back from the far right) -// // deeper child properties should be inlined with their parent's name -// // ie, this.[properties].owner.name should be this.ownerName - -// const proposedName = getPascalIdentifier(`${propertyName === 'properties' || /*objectProperties.length === 1*/ propertyName === 'error' ? '' : pascalCase(fixLeadingNumber(deconstruct(propertyName)).map(each => singularize(each)))} ${inlinedProperty.name}`); - -// const components = [...removeSequentialDuplicates([propertyName, ...inlinedProperty.nameComponents])]; -// virtualProperties.inlined.push({ -// name: proposedName, -// property: inlinedProperty.property, -// private: inlinedProperty.private, -// nameComponents: components, -// nameOptions: getNameOptions(inlinedProperty.property.schema.details.default.name, components), -// accessViaProperty: privateProperty, -// accessViaMember: inlinedProperty, -// accessViaSchema: schema, -// originalContainingSchema: schema, -// description: inlinedProperty.description, -// alias: [], -// required: inlinedProperty.required && privateProperty.required, -// }); -// } - - -// for (const inlinedProperty of [...virtualChildProperties.inlined]) { -// // child properties are be inlined without prefixing the name with the property name -// // unless there is a collision, in which case, we have to resolve - -// // (scan back from the far right) -// // deeper child properties should be inlined with their parent's name -// // ie, this.[properties].owner.name should be this.ownerName - - -// const proposedName = getPascalIdentifier(inlinedProperty.name); -// const components = [...removeSequentialDuplicates([propertyName, ...inlinedProperty.nameComponents])]; -// virtualProperties.inlined.push({ -// name: proposedName, -// property: inlinedProperty.property, -// private: inlinedProperty.private, -// nameComponents: components, -// nameOptions: getNameOptions(inlinedProperty.property.schema.details.default.name, components), -// accessViaProperty: privateProperty, -// accessViaMember: inlinedProperty, -// accessViaSchema: schema, -// originalContainingSchema: schema, -// description: inlinedProperty.description, -// alias: [], -// required: inlinedProperty.required && privateProperty.required -// }); -// } -// } else { -// // otherwise, we're not below the threshold, and we should treat this as a non-inlined property -// nonObjectProperties.push(property); -// } -// } - -// for (const property of nonObjectProperties) { -// const name = getPascalIdentifier(property.details.default.name); -// // this is not something that has properties, -// // so we don't need to do any inlining -// // however, we can add it to our list of virtual properties -// // so that our consumers can get it. -// virtualProperties.owned.push({ -// name, -// property, -// nameComponents: [name], -// nameOptions: [name], -// description: property.description || '', -// originalContainingSchema: schema, -// alias: [], -// required: property.details.default.required -// }); -// } - -// // resolve name collisions. -// const allProps = [...virtualProperties.owned, ...virtualProperties.inherited, ...virtualProperties.inlined]; -// const inlined = new Map(); - -// for (const each of allProps) { -// // track number of instances of a given name. -// inlined.set(each.name, (inlined.get(each.name) || 0) + 1); -// } - -// const usedNames = new Set(inlined.keys()); -// for (const each of virtualProperties.inlined.sort((a, b) => length(a.nameOptions) - length(b.nameOptions))) { -// const ct = inlined.get(each.name); -// if (ct && ct > 1) { -// // console.error(`Fixing collision on name ${each.name} #${ct} `); -// each.name = selectName(each.nameOptions, usedNames); -// } -// } -// schema.details.default.inline = 'yes'; -// return true; -// } +function createVirtualProperties(schema: ObjectSchema, stack: Array, threshold: number, conflicts: Array) { + // dolauli + // owned: all properties(obj & nonobj) in the schema, + // inherited: Properties from parents, + // inlined: for obj properties, flatten them to children, + // did we already inline this object + if (schema.language.default.inline === 'yes') { + return true; + } + + if (schema.language.default.inline === 'no') { + return false; + } + + // this is bad. This would happen when we have a circular reference in the tree. + // dolauli curious in which case this will happen, got it to use no-inline to skip inline and avoid circular reference + if (schema.language.default.inline === 'inprogress') { + let text = (`Note: during processing of '${schema.language.default.name}' a circular reference has been discovered.`); + text += '\n In order to proceed, you must add a directive to indicate which model you want to not inline.\n'; + text += '\ndirective:'; + text += '\n- no-inline: # choose ONE of these models to disable inlining'; + for (const each of stack) { + text += (`\n - ${each} `); + } + text += '\n'; + conflicts.push(text); + /* `directive: + - no-inline: + - MyModel + - YourModel + - HerModel + ` */ + + // `, and we're skipping inlining.\n ${stack.join(' => ')}`); + // mark it as 'not-inlining' + schema.language.default.inline = 'no'; + return false; + } + + // ok, set to in progress now. + schema.language.default.inline = 'inprogress'; + + // virutual property set. + const virtualProperties = schema.language.default.virtualProperties = { + owned: new Array(), + inherited: new Array(), + inlined: new Array(), + }; + + // First we should run thru the properties in parent classes and create inliners for each property they have. + // dolauli handle properties in parents + for (const parentSchema of values(schema.parents?.immediate)) { + // make sure that the parent is done. + + // Guess parent should always be an object. + if (!isObjectSchema(parentSchema)) + continue; + + createVirtualProperties(parentSchema, [...stack, `${schema.language.default.name}`], threshold, conflicts); + + const parentProperties = parentSchema.language.default.virtualProperties || { + owned: [], + inherited: [], + inlined: [], + }; + + // now we go thru the parent's virutal properties and create our own copies + for (const virtualProperty of [...parentProperties.inherited, ...parentProperties.inlined, ...parentProperties.owned]) { + // make sure that we have a list of shared owners of this property. + virtualProperty.sharedWith = virtualProperty.sharedWith || [virtualProperty]; + + // we are just copying over theirs to ours. + const inheritedProperty = { + name: virtualProperty.name, + property: virtualProperty.property, + private: virtualProperty.private, + nameComponents: virtualProperty.nameComponents, + nameOptions: virtualProperty.nameOptions, + accessViaProperty: virtualProperty, + accessViaMember: virtualProperty, + accessViaSchema: parentSchema, + originalContainingSchema: virtualProperty.originalContainingSchema, + description: virtualProperty.description, + alias: [], + required: virtualProperty.required, //|| !!values(schema.required).first(each => !!each && !!each.toLowerCase && each.toLowerCase() === virtualProperty.property.details.default.name.toLowerCase()), + sharedWith: virtualProperty.sharedWith, + }; + // add it to the list of virtual properties that share this property. + virtualProperty.sharedWith.push(inheritedProperty); + + // add it to this class. + virtualProperties.inherited.push(inheritedProperty); + } + } + + // dolauli figure out object properties and non object properties in this class + const [objectProperties, nonObjectProperties] = values(schema.properties).bifurcate(each => + !schema.language.default['skip-inline'] && // if this schema is marked skip-inline, none can be inlined, treat them all as straight properties. + !each.schema.language.default['skip-inline'] && // if the property schema is marked skip-inline, then it should not be processed either. + each.schema.type === SchemaType.Object && // is it an object + getAllProperties(each.schema).length > 0 // does it have properties (or inherit properties) + ); + + // run thru the properties in this class. + // dolauli handle properties in this class + for (const property of objectProperties) { + const propertyName = property.language.default.name; + + // for each object member, make sure that it's inlined it's children that it can. + createVirtualProperties(property.schema, [...stack, `${schema.language.default.name}`], threshold, conflicts); + + // this happens if there is a circular reference. + // this means that this class should not attempt any inlining of that property at all . + // dolauli pay attention to the condition check + const canInline = + // (!property.schema.details.default['skip-inline']) && + (!property.schema.language.default.byReference) && + // no additionalProperties in m4 + //(!(property.schema).additionalProperties) && + (property.schema).language.default.inline === 'yes'; + + // the target has properties that we can inline + const virtualChildProperties = property.schema.language.default.virtualProperties || { + owned: [], + inherited: [], + inlined: [], + }; + + const allNotRequired = values(getAllPublicVirtualProperties()).all(each => !each.property.language.default.required); + + const childCount = length(virtualChildProperties.owned) + length(virtualChildProperties.inherited) + length(virtualChildProperties.inlined); + if (canInline && (property.language.default.required || allNotRequired) && (childCount < threshold || propertyName === 'properties')) { + + + // if the child property is low enough (or it's 'properties'), let's create virtual properties for each one. + // create a private property for the inlined ones to use. + const privateProperty = { + name: getPascalIdentifier(propertyName), + propertySchema: schema, + property, + nameComponents: [getPascalIdentifier(propertyName)], + nameOptions: getNameOptions(schema.language.default.name, [propertyName]), + private: true, + description: property.summary || '', + originalContainingSchema: schema, + alias: [], + required: property.language.default.required, + }; + virtualProperties.owned.push(privateProperty); + + for (const inlinedProperty of [...virtualChildProperties.inherited, ...virtualChildProperties.owned]) { + // child properties are be inlined without prefixing the name with the property name + // unless there is a collision, in which case, we have to resolve + + // (scan back from the far right) + // deeper child properties should be inlined with their parent's name + // ie, this.[properties].owner.name should be this.ownerName + + const proposedName = getPascalIdentifier(`${propertyName === 'properties' || /*objectProperties.length === 1*/ propertyName === 'error' ? '' : pascalCase(fixLeadingNumber(deconstruct(propertyName)).map(each => singularize(each)))} ${inlinedProperty.name}`); + + const components = [...removeSequentialDuplicates([propertyName, ...inlinedProperty.nameComponents])]; + virtualProperties.inlined.push({ + name: proposedName, + property: inlinedProperty.property, + private: inlinedProperty.private, + nameComponents: components, + nameOptions: getNameOptions(inlinedProperty.property.schema.details.default.name, components), + accessViaProperty: privateProperty, + accessViaMember: inlinedProperty, + accessViaSchema: schema, + originalContainingSchema: schema, + description: inlinedProperty.description, + alias: [], + required: inlinedProperty.required && privateProperty.required, + }); + } + + + for (const inlinedProperty of [...virtualChildProperties.inlined]) { + // child properties are be inlined without prefixing the name with the property name + // unless there is a collision, in which case, we have to resolve + + // (scan back from the far right) + // deeper child properties should be inlined with their parent's name + // ie, this.[properties].owner.name should be this.ownerName + + + const proposedName = getPascalIdentifier(inlinedProperty.name); + const components = [...removeSequentialDuplicates([propertyName, ...inlinedProperty.nameComponents])]; + virtualProperties.inlined.push({ + name: proposedName, + property: inlinedProperty.property, + private: inlinedProperty.private, + nameComponents: components, + nameOptions: getNameOptions(inlinedProperty.property.schema.details.default.name, components), + accessViaProperty: privateProperty, + accessViaMember: inlinedProperty, + accessViaSchema: schema, + originalContainingSchema: schema, + description: inlinedProperty.description, + alias: [], + required: inlinedProperty.required && privateProperty.required + }); + } + } else { + // otherwise, we're not below the threshold, and we should treat this as a non-inlined property + nonObjectProperties.push(property); + } + } + + for (const property of nonObjectProperties) { + const name = getPascalIdentifier(property.language.default.name); + // this is not something that has properties, + // so we don't need to do any inlining + // however, we can add it to our list of virtual properties + // so that our consumers can get it. + virtualProperties.owned.push({ + name, + property, + nameComponents: [name], + nameOptions: [name], + description: property.summary || '', + originalContainingSchema: schema, + alias: [], + required: property.language.default.required + }); + } + + // resolve name collisions. + const allProps = [...virtualProperties.owned, ...virtualProperties.inherited, ...virtualProperties.inlined]; + const inlined = new Map(); + + for (const each of allProps) { + // track number of instances of a given name. + inlined.set(each.name, (inlined.get(each.name) || 0) + 1); + } + + const usedNames = new Set(inlined.keys()); + for (const each of virtualProperties.inlined.sort((a, b) => length(a.nameOptions) - length(b.nameOptions))) { + const ct = inlined.get(each.name); + if (ct && ct > 1) { + // console.error(`Fixing collision on name ${each.name} #${ct} `); + each.name = selectName(each.nameOptions, usedNames); + } + } + schema.language.default.inline = 'yes'; + return true; +} function createVirtualParameters(operation: CommandOperation) { // dolauli expand body parameter @@ -359,30 +366,30 @@ async function createVirtuals(state: State): Promise { // dolauli this plugin is used to expand the property in an object. // dolauli inline-threshold could be set in readme.md // skip-for-time-being - // const threshold = await state.getValue('inlining-threshold', 24); - // const conflicts = new Array(); - - // for (const schema of values(state.model.schemas)) { - // if (schema.type === JsonType.Object) { - // // did we already inline this object - // // dolauli skip if inlined - // if (schema.details.default.inlined) { - // continue; - // } - // // we have an object, let's process it. - - // createVirtualProperties(schema, new Array(), threshold, conflicts); - // } - // } - // if (length(conflicts) > 0) { - // // dolauli need to figure out how inline-properties is used in readme.md - // state.error('You have one or more circular references in your model, you must add configuration entries to specify which models won\'t be inlined.', ['inline-properties']); - // for (const each of conflicts) { - // state.error(each, ['circular reference']); - // } - // throw new Error('Circular references exists, must mark models as `no-inline`'); - // } - // dolauli update operations under commands + const threshold = await state.getValue('inlining-threshold', 24); + const conflicts = new Array(); + + for (const schema of values(state.model.schemas.objects)) { + + // did we already inline this object + // dolauli skip if inlined + if (schema.language.default.inlined) { + continue; + } + // we have an object, let's process it. + + createVirtualProperties(schema, new Array(), threshold, conflicts); + + } + if (length(conflicts) > 0) { + // dolauli need to figure out how inline-properties is used in readme.md + state.error('You have one or more circular references in your model, you must add configuration entries to specify which models won\'t be inlined.', ['inline-properties']); + for (const each of conflicts) { + state.error(each, ['circular reference']); + } + throw new Error('Circular references exists, must mark models as `no-inline`'); + } + //dolauli update operations under commands for (const operation of values(state.model.commands.operations)) { createVirtualParameters(operation); } diff --git a/powershell/plugins/plugin-tweak-model.ts b/powershell/plugins/plugin-tweak-model.ts index 9750101ed49..7712068e4b7 100644 --- a/powershell/plugins/plugin-tweak-model.ts +++ b/powershell/plugins/plugin-tweak-model.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { codeModelSchema, CodeModel, Schema, ObjectSchema, GroupSchema, isObjectSchema, SchemaType, GroupProperty, ParameterLocation, Operation, Parameter, VirtualParameter, getAllProperties, ImplementationLocation, OperationGroup, Request, SchemaContext } from '@azure-tools/codemodel'; +import { Property, codeModelSchema, CodeModel, Schema, StringSchema, ObjectSchema, GroupSchema, isObjectSchema, SchemaType, GroupProperty, ParameterLocation, Operation, Parameter, VirtualParameter, getAllProperties, ImplementationLocation, OperationGroup, Request, SchemaContext } from '@azure-tools/codemodel'; //import { ModelState } from '@azure-tools/codemodel-v3'; //import { KnownMediaType, knownMediaType, ParameterLocation, getPolymorphicBases, isSchemaObject, JsonType, Property, Schema, processCodeModel, StringFormat, codemodel, ModelState } from '@azure-tools/codemodel-v3'; import { pascalCase, deconstruct, fixLeadingNumber, serialize } from '@azure-tools/codegen'; @@ -12,6 +12,7 @@ import { NewModelState } from '../utils/model-state'; import { Channel, Host, Session, startSession } from '@azure-tools/autorest-extension-base'; import { defaultCipherList } from 'constants'; +import { NewString } from '../llcsharp/schema/string'; export const HeaderProperty = 'HeaderProperty'; export enum HeaderPropertyType { @@ -79,6 +80,75 @@ async function tweakModelV2(state: State): Promise { universalId.apiVersions = universalId.apiVersions || []; state.model.schemas.objects = state.model.schemas.objects || []; (universalId.language.default).uid = true; + + universalId.properties = universalId.properties || new Array(); + const urlParam = new Set(); + for (const operationGroup of values(model.operationGroups)) { + for (const operation of values(operationGroup.operations)) { + for (const param of values(operation.parameters).where(each => each.protocol.http?.in === ParameterLocation.Path)) { + const name = param.language.default.name; + + if (!urlParam.has(name)) { + urlParam.add(name); + universalId.addProperty(new Property(name, '', param.schema, { + serializedName: name, language: { + default: { + description: param.summary, + name: name, + required: false, + readOnly: false, + uid: 'universal-parameter:resource identity' + } + } + })); + } + } + } + } + + if (await state.getValue('azure', false)) { + universalId.addProperty(new Property('id', '', new StringSchema('_identity_type_', 'Resource identity path'), + { + serializedName: 'id', language: { + default: { + description: 'Resource identity path', + name: 'id', + required: false, + readOnly: false, + uid: 'universal-parameter:resource identity' + } + } + } + )); + } + // 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' + // } + // } + // }); + + + // 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' + // } + // } + // }); + // } state.model.schemas.objects.push(universalId); model.commands = { @@ -354,4 +424,4 @@ export async function tweakModelPlugin(service: Host) { //const result = tweakModelV2(session); await service.WriteFile('code-model-v4-tweakcodemodel-v2.yaml', serialize(await tweakModelV2(state)), undefined, 'code-model-v4'); //return processCodeModel(tweakModelV2, service, 'tweakcodemodel-v2'); -} +} \ No newline at end of file