diff --git a/powershell/cmdlets/class.ts b/powershell/cmdlets/class.ts index 30cef55d3bc..febc15e7298 100644 --- a/powershell/cmdlets/class.ts +++ b/powershell/cmdlets/class.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Schema as NewSchema, SchemaType, ArraySchema, SchemaResponse, HttpParameter, ObjectSchema } from '@azure-tools/codemodel'; +import { Schema as NewSchema, SchemaType, ArraySchema, SchemaResponse, HttpParameter, ObjectSchema, DictionarySchema } from '@azure-tools/codemodel'; import { command, getAllProperties, JsonType, http, getAllPublicVirtualProperties, getVirtualPropertyFromPropertyName, ParameterLocation, getAllVirtualProperties, VirtualParameter, VirtualProperty } from '@azure-tools/codemodel-v3'; import { CommandOperation, VirtualParameter as NewVirtualParameter } from '../utils/command-operation'; import { getAllProperties as NewGetAllProperties, getAllPublicVirtualProperties as NewGetAllPublicVirtualProperties, getVirtualPropertyFromPropertyName as NewGetVirtualPropertyFromPropertyName, VirtualProperty as NewVirtualProperty } from '../utils/schema'; @@ -2412,21 +2412,23 @@ export class NewCmdletClass extends Class { // } cmdletParameter.add(new Attribute(AllowEmptyCollectionAttribute)); } - //skip-for-time-being - // if (vSchema.additionalProperties) { - // // we have to figure out if this is a standalone dictionary or a hybrid object/dictionary. - // // if it's a hybrid, we have to create another parameter like -AdditionalProperties and have that dump the contents into the dictionary - // // if it's a standalone dictionary, we can just use hashtable instead - // if (length(vSchema.properties) === 0) { - // // it's a pure dictionary - // // add an attribute for changing the exported type. - // cmdletParameter.add(new Attribute(ExportAsAttribute, { parameters: [`typeof(${System.Collections.Hashtable})`] })); - // } else { - // // it's a hybrid. We need to add an additional property that puts its items into the target container - - // } - - // } + const dictSchema = vSchema.type === SchemaType.Dictionary ? vSchema : + vSchema.type === SchemaType.Object ? (vSchema).parents?.immediate?.find((s) => s.type === SchemaType.Dictionary) : + undefined; + if (dictSchema) { + // we have to figure out if this is a standalone dictionary or a hybrid object/dictionary. + // if it's a hybrid, we have to create another parameter like -AdditionalProperties and have that dump the contents into the dictionary + // if it's a standalone dictionary, we can just use hashtable instead + if (length((vSchema).properties) === 0) { + // it's a pure dictionary + // add an attribute for changing the exported type. + cmdletParameter.add(new Attribute(ExportAsAttribute, { parameters: [`typeof(${System.Collections.Hashtable})`] })); + } else { + // it's a hybrid. We need to add an additional property that puts its items into the target container + + } + + } const desc = (vParam.description || '.').replace(/[\r?\n]/gm, ''); cmdletParameter.description = desc; @@ -2478,31 +2480,34 @@ export class NewCmdletClass extends Class { this.add(cmdletParameter); } - // skip-for-time-being - // if (parameter.schema.additionalProperties) { - // // if there is an additional properties on this type - // // add a hashtable parameter for additionalProperties - // let apPropName = ''; - // const options = ['AdditionalProperties', 'MoreProperties', 'ExtendedProperties', 'Properties']; - // for (const n of options) { - // if (this.properties.find(each => each.name === n)) { - // continue; - // } - // apPropName = n; - // break; - // } - - // this.apProp = this.add(new Property(apPropName, System.Collections.Hashtable)); - // this.apProp.add(new Attribute(ParameterAttribute, { - // parameters: ['Mandatory = false', 'HelpMessage = "Additional Parameters"'] - // })); - // this.bodyParameterInfo = { - // type: { - // declaration: parameter.schema.details.csharp.fullname - // }, - // valueType: parameter.schema.additionalProperties === true ? System.Object : this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(parameter.schema.additionalProperties, true, this.state) - // }; - // } + const paramDictSchema = parameter.schema.type === SchemaType.Dictionary ? parameter.schema : + parameter.schema.type === SchemaType.Object ? (parameter.schema).parents?.immediate?.find((s) => s.type === SchemaType.Dictionary) : + undefined; + if (paramDictSchema) { + // if there is an additional properties on this type + // add a hashtable parameter for additionalProperties + let apPropName = ''; + const options = ['AdditionalProperties', 'MoreProperties', 'ExtendedProperties', 'Properties']; + for (const n of options) { + if (this.properties.find(each => each.name === n)) { + continue; + } + apPropName = n; + break; + } + + this.apProp = this.add(new Property(apPropName, System.Collections.Hashtable)); + this.apProp.add(new Attribute(ParameterAttribute, { + parameters: ['Mandatory = false', 'HelpMessage = "Additional Parameters"'] + })); + this.bodyParameterInfo = { + type: { + declaration: parameter.schema.language.csharp?.fullname + }, + valueType: (paramDictSchema).elementType.type === SchemaType.Any ? System.Object : + this.state.project.schemaDefinitionResolver.resolveTypeDeclaration((paramDictSchema).elementType, true, this.state) + }; + } this.bodyParameter = expandedBodyParameter; continue; @@ -2550,22 +2555,24 @@ export class NewCmdletClass extends Class { description: vParam.description })); - // skip-for-time-being - // if (vSchema.additionalProperties) { - // // we have to figure out if this is a standalone dictionary or a hybrid object/dictionary. - // // if it's a hybrid, we have to create another parameter like -AdditionalProperties and have that dump the contents into the dictionary - // // if it's a standalone dictionary, we can just use hashtable instead - // if (length(vSchema.properties) === 0) { - // // it's a pure dictionary - // // change the property type to hashtable. - // // add an attribute to change the exported type. - // regularCmdletParameter.add(new Attribute(ExportAsAttribute, { parameters: [`typeof(${System.Collections.Hashtable})`] })); - // } else { - // // it's a hybrid. We need to add an additional property that puts its items into the target container - - // } - - // } + const dictSchema = vSchema.type === SchemaType.Dictionary ? vSchema : + vSchema.type === SchemaType.Object ? (vSchema).parents?.immediate?.find((s) => s.type === SchemaType.Dictionary) : + undefined; + if (dictSchema) { + // we have to figure out if this is a standalone dictionary or a hybrid object/dictionary. + // if it's a hybrid, we have to create another parameter like -AdditionalProperties and have that dump the contents into the dictionary + // if it's a standalone dictionary, we can just use hashtable instead + if (length((vSchema).properties) === 0) { + // it's a pure dictionary + // change the property type to hashtable. + // add an attribute to change the exported type. + regularCmdletParameter.add(new Attribute(ExportAsAttribute, { parameters: [`typeof(${System.Collections.Hashtable})`] })); + } else { + // it's a hybrid. We need to add an additional property that puts its items into the target container + + } + + } this.thingsToSerialize.push(regularCmdletParameter); diff --git a/powershell/llcsharp/model/model-class-dictionary.ts b/powershell/llcsharp/model/model-class-dictionary.ts index 728e369caee..330cdd2f3e9 100644 --- a/powershell/llcsharp/model/model-class-dictionary.ts +++ b/powershell/llcsharp/model/model-class-dictionary.ts @@ -3,11 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Field, System, Property, toExpression, dotnet, Parameter, ParameterModifier, Method, Class, TypeDeclaration, Indexer, Access, Variable, Expression, If, And, ForEach, LocalVariable, ImplicitCastOperator } from '@azure-tools/codegen-csharp'; -import { ModelClass } from './model-class'; -import { EnhancedTypeDeclaration } from '../schema/extended-type-declaration'; +import { ModelClass, NewModelClass } from './model-class'; +import { EnhancedTypeDeclaration, NewEnhancedTypeDeclaration } from '../schema/extended-type-declaration'; import { ClientRuntime } from '../clientruntime'; import { getAllVirtualProperties } from '@azure-tools/codemodel-v3'; import { DeepPartial } from '@azure-tools/codegen'; +import { DictionarySchema, ObjectSchema, SchemaType, Schema } from '@azure-tools/codemodel'; export class DictionaryImplementation extends Class { private get state() { return this.modelClass.state; } @@ -114,3 +115,110 @@ export class DictionaryImplementation extends Class { return `${super.fileName}.dictionary`; } } + +export class NewDictionaryImplementation extends Class { + private get state() { return this.modelClass.state; } + private get schema() { return this.modelClass.schema; } + public valueType!: TypeDeclaration | NewEnhancedTypeDeclaration; + public ownsDictionary = false; + + constructor(protected modelClass: NewModelClass, objectInitializer?: DeepPartial) { + super(modelClass.namespace, modelClass.name); + this.apply(objectInitializer); + } + + init(valueType?: TypeDeclaration, accessViaMember?: Expression) { + if (valueType && accessViaMember) { + this.valueType = valueType; + this.implementIDictionary(this, '', System.String, valueType, accessViaMember); + + } + else { + const dictSchema = (this.schema).type === SchemaType.Dictionary ? this.schema : this.schema.parents?.immediate?.find((s) => s.type === SchemaType.Dictionary); + if (dictSchema) { + this.ownsDictionary = true; + this.valueType = (dictSchema).elementType.type === SchemaType.Any ? System.Object : this.state.project.modelsNamespace.NewResolveTypeDeclaration((dictSchema).elementType, true, this.state); + this.modelClass.modelInterface.interfaces.push(this.implementIDictionary(this, 'additionalProperties', System.String, this.valueType)); + } + } + + return this; + } + + addSerialization() { + if (this.modelClass.jsonSerializer) { + // add serializer methods. + // this.modelClass.jsonSerializer + + + } + } + + implementIDictionary(targetClass: Class, name: string, keyType: TypeDeclaration, valueType: TypeDeclaration, accessViaMember?: Expression) { + const containerInterfaceType = { declaration: `${ClientRuntime}.IAssociativeArray<${valueType.declaration}>`, allProperties: [] }; + const dictionaryInterfaceType = System.Collections.Generic.IDictionary(keyType, valueType); + const itemType = System.Collections.Generic.KeyValuePair(keyType, valueType); + + // add the interface to the list of interfaces for the class + targetClass.interfaces.push(containerInterfaceType); + + // the backing field + const dictionaryType = System.Collections.Generic.Dictionary(keyType, valueType); + + accessViaMember = accessViaMember || targetClass.add(new Field(`__${name}`, dictionaryType, { access: Access.Protected, initialValue: dictionaryType.new() })); + + const indexer = targetClass.add(new Indexer(keyType, valueType, { get: toExpression(`${accessViaMember}[index]`), set: toExpression(`${accessViaMember}[index] = value`) })); + + // the parameters used in methods. + + const pKey = new Parameter('key', keyType); + const pValue = new Parameter('value', valueType); + const pOutValue = new Parameter('value', valueType, { modifier: ParameterModifier.Out }); + + targetClass.add(new Property(`${containerInterfaceType.declaration}.Keys`, System.Collections.Generic.IEnumerable(keyType), { get: toExpression(`${accessViaMember}.Keys`), getAccess: Access.Explicit })); + targetClass.add(new Property(`${containerInterfaceType.declaration}.Values`, System.Collections.Generic.IEnumerable(valueType), { get: toExpression(`${accessViaMember}.Values`), getAccess: Access.Explicit })); + targetClass.add(new Property(`${containerInterfaceType.declaration}.Count`, dotnet.Int, { get: toExpression(`${accessViaMember}.Count`), getAccess: Access.Explicit })); + if (name) { + targetClass.add(new ImplicitCastOperator(dictionaryType, targetClass, `source.${accessViaMember}`)); + targetClass.add(new Property(`${containerInterfaceType.declaration}.AdditionalProperties`, dictionaryInterfaceType, { get: toExpression(`${accessViaMember}`), getAccess: Access.Explicit })); + } else { + targetClass.add(new Property(`${containerInterfaceType.declaration}.AdditionalProperties`, dictionaryInterfaceType, { get: toExpression(`${accessViaMember}.AdditionalProperties`), getAccess: Access.Explicit })); + } + targetClass.add(new Method('Add', dotnet.Void, { parameters: [pKey, pValue], body: toExpression(`${accessViaMember}.Add( ${pKey}, ${pValue})`), access: Access.Public })); + targetClass.add(new Method('Clear', dotnet.Void, { body: toExpression(`${accessViaMember}.Clear()`), access: Access.Public })); + + targetClass.add(new Method('ContainsKey', dotnet.Bool, { parameters: [pKey], body: toExpression(`${accessViaMember}.ContainsKey( ${pKey})`), access: Access.Public })); + + targetClass.add(new Method('Remove', dotnet.Bool, { parameters: [pKey], body: toExpression(`${accessViaMember}.Remove( ${pKey})`), access: Access.Public })); + + targetClass.add(new Method('TryGetValue', dotnet.Bool, { parameters: [pKey, pOutValue], body: toExpression(`${accessViaMember}.TryGetValue( ${pKey}, out ${pOutValue})`), access: Access.Public })); + + const all = getAllVirtualProperties(this.schema.language.csharp?.virtualProperties); + const exclusions = all.map(each => `"${each.name}"`).join(','); + + // add a CopyFrom that takes an IDictionary or PSObject and copies the values into this dictionary + for (const pDictType of [System.Collections.IDictionary, { declaration: 'global::System.Management.Automation.PSObject' }]) { + const pDict = new Parameter('source', pDictType); + targetClass.add(new Method('CopyFrom', dotnet.Void, { + parameters: [pDict], body: function* () { + yield If(pDict.IsNotNull, function* () { + + yield ForEach('property', ` Microsoft.Rest.ClientRuntime.PowerShell.TypeConverterExtensions.GetFilteredProperties(${pDict.value}, ${System.Collections.Generic.HashSet(System.String).new()} { ${exclusions} } )`, function* () { + + yield If(And('null != property.Key', 'null != property.Value'), function* () { + yield `this.${accessViaMember}.Add(property.Key.ToString(), global::System.Management.Automation.LanguagePrimitives.ConvertTo<${valueType.declaration}>( property.Value));`; + }); + }); + }); + } + })); + } + + // return dictionaryInterfaceType; + return containerInterfaceType; + } + + public get fileName(): string { + return `${super.fileName}.dictionary`; + } +} diff --git a/powershell/llcsharp/model/model-class-json.ts b/powershell/llcsharp/model/model-class-json.ts index 25ff3258130..08496a9350c 100644 --- a/powershell/llcsharp/model/model-class-json.ts +++ b/powershell/llcsharp/model/model-class-json.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 { Schema as NewSchema, ObjectSchema } from '@azure-tools/codemodel'; +import { Schema as NewSchema, ObjectSchema, SchemaType } from '@azure-tools/codemodel'; import { KnownMediaType, HeaderProperty, HeaderPropertyType, getAllProperties } from '@azure-tools/codemodel-v3'; import { getAllProperties as newGetAllProperties } from '@azure-tools/codemodel'; import { EOL, DeepPartial, } from '@azure-tools/codegen'; @@ -25,7 +25,7 @@ import { ClientRuntime } from '../clientruntime'; import { dotnet } from '@azure-tools/codegen-csharp'; import { ModelClass, NewModelClass } from './model-class'; -import { EnhancedTypeDeclaration } from '../schema/extended-type-declaration'; +import { EnhancedTypeDeclaration, NewEnhancedTypeDeclaration } from '../schema/extended-type-declaration'; import { popTempVar, pushTempVar } from '../schema/primitive'; import { ModelProperty } from './property'; @@ -310,8 +310,11 @@ export class NewJsonSerializableClass extends Class { for (const each of values(modelClass.backingFields)) { serializeStatements.add(`${each.field.value}?.ToJson(${container}, ${mode.use});`); - const sch = (each.typeDeclaration).schema; - if (!(sch instanceof NewSchema) && (sch.additionalProperties)) { + const sch = (each.typeDeclaration).schema; + const dictSchema = sch.type === SchemaType.Dictionary ? sch : + sch.type === SchemaType.Object ? (sch).parents?.immediate.find((s) => s.type === SchemaType.Dictionary) : + undefined; + if (dictSchema) { deserializeStatements.add(`${each.field.value} = new ${each.className}(json${this.excludes});`); } else { deserializeStatements.add(`${each.field.value} = new ${each.className}(json);`); diff --git a/powershell/llcsharp/model/model-class-serializer.ts b/powershell/llcsharp/model/model-class-serializer.ts index 8b9a3c7422d..9ba766976a4 100644 --- a/powershell/llcsharp/model/model-class-serializer.ts +++ b/powershell/llcsharp/model/model-class-serializer.ts @@ -30,7 +30,7 @@ import { popTempVar, pushTempVar } from '../schema/primitive'; import { ModelProperty } from './property'; import { ObjectImplementation } from '../schema/object'; import { Schema } from '../code-model'; -import { Schema as NewSchema } from '@azure-tools/codemodel'; +import { DictionarySchema, ObjectSchema, Schema as NewSchema, SchemaType } from '@azure-tools/codemodel'; import { getVirtualPropertyName, NewGetVirtualPropertyName } from './model-class'; import { VirtualProperty as NewVirtualProperty } from '../../utils/schema'; @@ -244,27 +244,33 @@ export class NewDeserializerPartialClass extends NewSerializationPartialClass { yield `${$this.beforeDeserialize.name}(${$this.contentParameter}, ref ${returnNow.value});`; yield If(returnNow, 'return;'); yield $this.deserializeStatements; - // skip-for-time-being - // if ($this.hasAadditionalProperties($this.schema)) { - // // this type has an additional properties dictionary - // yield '// this type is a dictionary; copy elements from source to here.'; - // yield `CopyFrom(${$this.contentParameter.value});`; - // } + + if ($this.hasAadditionalProperties($this.schema)) { + // this type has an additional properties dictionary + yield '// this type is a dictionary; copy elements from source to here.'; + yield `CopyFrom(${$this.contentParameter.value});`; + } yield `${$this.afterDeserialize.name}(${$this.contentParameter});`; }); } - private hasAadditionalProperties(aSchema: Schema): boolean { - if (aSchema.additionalProperties) { + private hasAadditionalProperties(aSchema: NewSchema): boolean { + if (aSchema.type === SchemaType.Dictionary) { return true; - } else - for (const each of values(aSchema.allOf)) { - const r = this.hasAadditionalProperties(each); - if (r) { - return r; - } + } + if (aSchema.type !== SchemaType.Object) { + return false; + } + const objSchema = (aSchema).parents?.immediate; + if (!objSchema || objSchema.length === 0) { + return false; + } + for (const parent of objSchema) { + if (this.hasAadditionalProperties(parent)) { + return true; } + } return false; } diff --git a/powershell/llcsharp/model/model-class.ts b/powershell/llcsharp/model/model-class.ts index ecacf582823..c87075ae1bd 100644 --- a/powershell/llcsharp/model/model-class.ts +++ b/powershell/llcsharp/model/model-class.ts @@ -16,8 +16,8 @@ import { JsonSerializableClass, NewJsonSerializableClass } from './model-class-j 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, Schema as NewSchema } from '@azure-tools/codemodel'; +import { DictionaryImplementation, NewDictionaryImplementation } from './model-class-dictionary'; +import { Languages, Language, Schema as NewSchema, SchemaType, ObjectSchema, DictionarySchema } from '@azure-tools/codemodel'; import { VirtualProperty as NewVirtualProperty, getAllVirtualProperties as newGetAllVirtualProperties } from '../../utils/schema'; export function getVirtualPropertyName(vp?: VirtualProperty): string { @@ -563,7 +563,7 @@ export class NewModelClass extends Class implements NewEnhancedTypeDeclaration { /* @internal */ validationEventListener: Parameter = new Parameter('eventListener', ClientRuntime.IEventListener, { description: `an instance that will receive validation events.` }); /* @internal */ jsonSerializer?: NewJsonSerializableClass; // /* @internal */ xmlSerializer?: XmlSerializableClass; - /* @internal */ dictionaryImpl?: DictionaryImplementation; + /* @internal */ dictionaryImpl?: NewDictionaryImplementation; private readonly validationStatements = new Statements(); public ownedProperties = new Array(); @@ -608,13 +608,14 @@ export class NewModelClass extends Class implements NewEnhancedTypeDeclaration { // add default constructor this.addMethod(new Constructor(this, { description: `Creates an new instance.` })); // default constructor for fits and giggles. - // skip-for-time-being // handle parent interface implementation if (!this.handleAllOf()) { // handle the AdditionalProperties if used - // if (this.schema.additionalProperties) { - // this.dictionaryImpl = new DictionaryImplementation(this).init(); - // } + const dictSchema = (this.schema).type === SchemaType.Dictionary ? this.schema : + this.schema.parents?.immediate?.find((schema) => schema.type === SchemaType.Dictionary); + if (dictSchema) { + this.dictionaryImpl = new NewDictionaryImplementation(this).init(); + } } // create the properties for ths schema @@ -684,7 +685,7 @@ 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; @@ -838,23 +839,25 @@ export class NewModelClass extends Class implements NewEnhancedTypeDeclaration { } private additionalPropertiesType(aSchema: NewSchema): TypeDeclaration | undefined { - // skip-for-time-being - // if (aSchema.additionalProperties) { - - // if (aSchema.additionalProperties === true) { - // return System.Object; - - // } else { - // // we're going to implement IDictionary - // return this.state.project.modelsNamespace.resolveTypeDeclaration(aSchema.additionalProperties, true, this.state); - // } - // } else - // for (const each of values(aSchema.allOf)) { - // const r = this.additionalPropertiesType(each); - // if (r) { - // return r; - // } - // } + const schema = aSchema.type === SchemaType.Dictionary ? aSchema : + aSchema.type === SchemaType.Object ? (aSchema).parents?.immediate?.find((s) => s.type === SchemaType.Dictionary) : + undefined; + if (schema) { + const dictSchema = schema as DictionarySchema; + if (dictSchema.elementType.type === SchemaType.Any) { + return System.Object; + + } else { + // we're going to implement IDictionary + return this.state.project.modelsNamespace.NewResolveTypeDeclaration(dictSchema.elementType, true, this.state); + } + } else + for (const each of values((aSchema).parents?.immediate)) { + const r = this.additionalPropertiesType(each); + if (r) { + return r; + } + } return undefined; } @@ -863,6 +866,9 @@ export class NewModelClass extends Class implements NewEnhancedTypeDeclaration { // handle s // add an 'implements' for the interface for the allOf. for (const { key: eachSchemaIndex, value: eachSchemaValue } of items(this.schema.parents?.immediate)) { + if (eachSchemaValue.type === SchemaType.Dictionary) { + continue; + } const aSchema = eachSchemaValue; const aState = this.state.path('allOf', eachSchemaIndex); @@ -890,8 +896,8 @@ export class NewModelClass extends Class implements NewEnhancedTypeDeclaration { // const addlPropType = this.additionalPropertiesType(aSchema); if (addlPropType) { - // this.dictionaryImpl = new DictionaryImplementation(this).init(addlPropType, backingField); - // hasAdditionalPropertiesInParent = true; + this.dictionaryImpl = new NewDictionaryImplementation(this).init(addlPropType, backingField); + hasAdditionalPropertiesInParent = true; } } return hasAdditionalPropertiesInParent; diff --git a/powershell/llcsharp/model/property.ts b/powershell/llcsharp/model/property.ts index dd21d43c46e..0c87dd95aef 100644 --- a/powershell/llcsharp/model/property.ts +++ b/powershell/llcsharp/model/property.ts @@ -139,7 +139,7 @@ export class NewModelProperty extends BackedProperty implements EnhancedVariable this.apply(objectInitializer); this.description = description; this.required = isRequired; - if (this.schema.type === SchemaType.Object && isAnExpression(this.get) && schema.language.csharp?.classImplementation) { + if ((this.schema.type === SchemaType.Object || this.schema.type === SchemaType.Dictionary) && 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}())`); } diff --git a/powershell/llcsharp/schema/schema-resolver.ts b/powershell/llcsharp/schema/schema-resolver.ts index b8ddd1006eb..fcb837fc3c3 100644 --- a/powershell/llcsharp/schema/schema-resolver.ts +++ b/powershell/llcsharp/schema/schema-resolver.ts @@ -174,6 +174,8 @@ export class NewSchemaDefinitionResolver { return new NewArrayOf(schema, required, elementType, ar.minItems, ar.maxItems, ar.uniqueItems); } + case SchemaType.Any: + case SchemaType.Dictionary: case SchemaType.Object: { const result = schema.language.csharp && this.cache.get(schema.language.csharp.fullname || ''); if (result) { diff --git a/powershell/models/model-extensions.ts b/powershell/models/model-extensions.ts index 477c19d8e36..2032f7f4675 100644 --- a/powershell/models/model-extensions.ts +++ b/powershell/models/model-extensions.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 { Schema as NewSchema } from '@azure-tools/codemodel'; +import { DictionarySchema, ObjectSchema, Schema as NewSchema, SchemaType } from '@azure-tools/codemodel'; import { items, values, keys, Dictionary, length } from '@azure-tools/linq'; import { Catch, Try, Else, ElseIf, If, Interface, Attribute, Parameter, Modifier, dotnet, Class, LambdaMethod, LiteralExpression, Method, Namespace, System, Return, LocalVariable, Constructor, IsAssignableFrom, ImportDirective, Property, Access, InterfaceProperty } from '@azure-tools/codegen-csharp'; import { Schema, ClientRuntime, SchemaDefinitionResolver, ObjectImplementation, NewObjectImplementation, DeserializerPartialClass, NewSchemaDefinitionResolver, NewDeserializerPartialClass } from '../llcsharp/exports'; @@ -279,11 +279,66 @@ export class NewModelExtensionsNamespace extends Namespace { // Add typeconverters to model classes (partial) for (const schemaGroup of values(schemas)) { + if (schemaGroup.length > 0 && (schemaGroup[0].type === SchemaType.Any)) { + // skip any + continue; + } for (const schema of values(schemaGroup)) { if (!schema || (schema.language.csharp && schema.language.csharp.skip)) { continue; } + if (schema.type === SchemaType.Dictionary) { + // xichen: + // Case1: dictionary schema is only used in parents: + // "definitions": { + // "PetAPInPropertiesWithAPString": { + // "type": "object", + // "properties": { + // "id": { + // "type": "integer" + // } + // }, + // "additionalProperties": { + // "type": "string" + // } + // } + // } + // + // Case2: we will have a dictionary property: + // "definitions": { + // "PetAPInProperties": { + // "type": "object", + // "properties": { + // "id": { + // "type": "integer" + // }, + // "additionalProperties": { + // "type": "object", + // "additionalProperties": { + // "type": "number" + // } + // } + // } + // } + // } + // + // We only create model class for case 2, because in m3 case 2 will has independent scheam for additionalProperties, + // But for case 1, PetAPInPropertiesWithAPString will only have additionalProperties. + // + // This is to make generated code same as m3. Actually there wont be side effect if we skip this check. + const objSchemas = schemas['objects']; + const usedByProp = schemas['objects']?.some((objSchema) => { + if ((objSchema).properties?.some((prop) => prop.schema === schema)) { + return true; + } + return false; + }); + if (!usedByProp) { + continue; + } + } + const td = this.resolver.resolveTypeDeclaration(schema, true, state); if (td instanceof NewObjectImplementation) { diff --git a/powershell/plugins/cs-namer-v2.ts b/powershell/plugins/cs-namer-v2.ts index ac6aa0c28da..3da5268c873 100644 --- a/powershell/plugins/cs-namer-v2.ts +++ b/powershell/plugins/cs-namer-v2.ts @@ -95,7 +95,7 @@ function setSchemaNames(schemaGroups: Dictionary>, azure: boolean, thisNamespace.add(schemaName); // object types. - if (schema.type === SchemaType.Object) { + if (schema.type === SchemaType.Object || schema.type === SchemaType.Dictionary) { schema.language.csharp = { ...details, apiversion: thisApiversion, diff --git a/powershell/plugins/plugin-create-inline-properties.ts b/powershell/plugins/plugin-create-inline-properties.ts index c3fa1261842..0e85e2f05d5 100644 --- a/powershell/plugins/plugin-create-inline-properties.ts +++ b/powershell/plugins/plugin-create-inline-properties.ts @@ -158,11 +158,11 @@ function createVirtualProperties(schema: ObjectSchema, stack: Array, thr // 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 isDict = property.schema.type === SchemaType.Dictionary || (property.schema).parents?.immediate?.find((s) => s.type === SchemaType.Dictionary); const canInline = // (!property.schema.details.default['skip-inline']) && (!property.schema.language.default.byReference) && - // no additionalProperties in m4 - //(!(property.schema).additionalProperties) && + (!isDict) && (property.schema).language.default.inline === 'yes'; // the target has properties that we can inline @@ -190,7 +190,7 @@ function createVirtualProperties(schema: ObjectSchema, stack: Array, thr description: property.summary || '', originalContainingSchema: schema, alias: [], - required: !!property.required, + required: property.required || property.language.default.required, }; virtualProperties.owned.push(privateProperty); @@ -268,7 +268,7 @@ function createVirtualProperties(schema: ObjectSchema, stack: Array, thr description: property.summary || '', originalContainingSchema: schema, alias: [], - required: !!property.required + required: property.required || property.language.default.required }); } diff --git a/powershell/plugins/plugin-tweak-m4-model.ts b/powershell/plugins/plugin-tweak-m4-model.ts index 7cb8e0f7413..0f335587d91 100644 --- a/powershell/plugins/plugin-tweak-m4-model.ts +++ b/powershell/plugins/plugin-tweak-m4-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 { CodeModel, HttpHeader, ObjectSchema, Property } from '@azure-tools/codemodel'; +import { CodeModel, DictionarySchema, getAllProperties, HttpHeader, ObjectSchema, Property, Schema, SchemaType } from '@azure-tools/codemodel'; import { serialize } from '@azure-tools/codegen'; import { PwshModel } from '../utils/PwshModel'; import { NewModelState } from '../utils/model-state'; @@ -10,7 +10,6 @@ import { StatusCodes } from '../utils/http-definitions'; import { Host } from '@azure-tools/autorest-extension-base'; - type State = NewModelState; async function tweakModel(state: State): Promise { @@ -18,6 +17,10 @@ async function tweakModel(state: State): Promise { addResponseHeaderSchema(model); + addDictionaryApiVersion(model); + + removeDictionaryDefaultDescription(model); + return model; } @@ -76,8 +79,60 @@ function addResponseHeaderSchema(model: CodeModel) { }); } +function addDictionaryApiVersion(model: CodeModel) { + // If object has dictionary property, this property's schema does not have api version information in m4. We should add this back. + + model.schemas.objects?.forEach((schema) => { + if (!schema.apiVersions) { + return; + } + + for (const prop of getAllProperties(schema)) { + if (prop.schema.type !== SchemaType.Dictionary || prop.schema.apiVersions) { + continue; + } + const dictSchema = prop.schema as DictionarySchema; + dictSchema.apiVersions = JSON.parse(JSON.stringify(schema.apiVersions)); + } + }) +} + +function removeDictionaryDefaultDescription(model: CodeModel) { + // For dictionary schema and property, if there is no description assigned, m4 will set a default description like: Dictionary of + // To keep same action as m3, we will set it to empty string + + const visited = new Set(); + [...model.schemas.objects ?? [], ...model.schemas.dictionaries ?? []].forEach((schema) => { + recursiveRemoveDefaultDescription(schema, visited); + }) +} + +function recursiveRemoveDefaultDescription(schema: Schema, visited: Set) { + if (visited.has(schema) || (schema.type !== SchemaType.Object && schema.type !== SchemaType.Dictionary)) { + return; + } + // Default description pattern in m4 + const defaultDescPattern = /Dictionary of <.*>$/; + visited.add(schema); + if (schema.type === SchemaType.Dictionary) { + const dictSchema = schema as DictionarySchema; + recursiveRemoveDefaultDescription(dictSchema.elementType, visited); + if (defaultDescPattern.test(dictSchema.language.default.description)) { + dictSchema.language.default.description = ''; + } + } else if (schema.type === SchemaType.Object) { + const objSchema = schema as ObjectSchema; + for (const prop of getAllProperties(objSchema)) { + recursiveRemoveDefaultDescription(prop.schema, visited); + if (prop.schema.type === SchemaType.Dictionary && defaultDescPattern.test(prop.language.default.description)) { + prop.language.default.description = ''; + } + } + } +} + export async function tweakM4ModelPlugin(service: Host) { const state = await new NewModelState(service).init(); - await service.WriteFile('code-model-v4-tweakm4codemodel.yaml', serialize(await tweakModel(state)), undefined, 'code-model-v4'); -} \ No newline at end of file + service.WriteFile('code-model-v4-tweakm4codemodel.yaml', serialize(await tweakModel(state)), undefined, 'code-model-v4'); +}