powershell/llcsharp/model/model-class-serializer.ts (145 lines of code) (raw):

/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { KnownMediaType, HeaderProperty, HeaderPropertyType, getAllProperties, JsonType, VirtualProperty } from '@azure-tools/codemodel-v3'; import { Initializer, EOL, DeepPartial } from '@azure-tools/codegen'; import { items, values, } from '@azure-tools/linq'; import { Access, Modifier, StringExpression, Expression, System, TypeContainer, TypeDeclaration, LocalVariable, And } from '@azure-tools/codegen-csharp'; import { Class } from '@azure-tools/codegen-csharp'; import { Constructor } from '@azure-tools/codegen-csharp'; import { IsDeclaration, toExpression } from '@azure-tools/codegen-csharp'; import { Method, PartialMethod } from '@azure-tools/codegen-csharp'; import { Parameter } from '@azure-tools/codegen-csharp'; import { ParameterModifier } from '@azure-tools/codegen-csharp'; import { TerminalCase } from '@azure-tools/codegen-csharp'; import { If, Not, ForEach } from '@azure-tools/codegen-csharp'; import { Return } from '@azure-tools/codegen-csharp'; import { Statements } from '@azure-tools/codegen-csharp'; import { Switch } from '@azure-tools/codegen-csharp'; import { Ternery } from '@azure-tools/codegen-csharp'; import { ClientRuntime } from '../clientruntime'; import { dotnet } from '@azure-tools/codegen-csharp'; import { EnhancedTypeDeclaration } from '../schema/extended-type-declaration'; import { popTempVar, pushTempVar } from '../schema/primitive'; import { ObjectImplementation } from '../schema/object'; import { Schema } from '../code-model'; import { DictionarySchema, ObjectSchema, Schema as NewSchema, SchemaType } from '@autorest/codemodel'; import { getVirtualPropertyName } from './model-class'; import { VirtualProperty as NewVirtualProperty } from '../../utils/schema'; export class SerializationPartialClass extends Initializer { constructor(protected targetClass: Class, protected targetInterface: TypeDeclaration, protected serializationType: TypeDeclaration, protected serializationFormat: string, protected schema: NewSchema, protected resolver: (s: NewSchema, req: boolean) => EnhancedTypeDeclaration, objectInitializer?: DeepPartial<SerializationPartialClass>) { super(); this.apply(objectInitializer); } protected get virtualProperties() { return this.schema.language.csharp?.virtualProperties || { owned: [], inherited: [], inlined: [] }; } protected get allVirtualProperties() { const vp = this.virtualProperties; // return [...vp.owned, ...vp.inherited, ...vp.inlined]; return values(vp.owned, vp.inherited, vp.inlined).toArray(); } protected contentParameter = new Parameter('content', this.serializationType, { description: `The ${this.serializationType.declaration} content that should be used.` }); protected refContainerParameter = new Parameter('container', this.serializationType, { modifier: ParameterModifier.Ref, description: `The ${this.serializationType.declaration} container that the serialization result will be placed in.` }); protected returnNowParameter = new Parameter('returnNow', dotnet.Bool, { modifier: ParameterModifier.Ref, description: 'Determines if the rest of the serialization should be processed, or if the method should return instantly.' }); protected get typeCref() { return `<see cref="${this.serializationType.declaration}" />`; } protected get thisCref() { return `<see cref="${this.targetClass.declaration}" />`; } protected get interfaceCref() { return `<see cref="${this.targetInterface.declaration}" />`; } } export class DeserializerPartialClass extends SerializationPartialClass { private beforeDeserialize!: Method; private afterDeserialize!: Method; constructor(targetClass: Class, targetInterface: TypeDeclaration, protected serializationType: TypeDeclaration, protected serializationFormat: string, protected schema: NewSchema, resolver: (s: NewSchema, req: boolean) => EnhancedTypeDeclaration, objectInitializer?: DeepPartial<DeserializerPartialClass>) { super(targetClass, targetInterface, serializationType, serializationFormat, schema, resolver); this.apply(objectInitializer); } async init() { // add partial methods for extensibility this.addPartialMethods(); this.addDeserializerConstructor(); this.addDeserializerMethod(); } protected addDeserializerConstructor() { const $this = this; const deserializerConstructor = this.targetClass.addMethod(new Constructor(this.targetClass, { parameters: [this.contentParameter], access: Access.Internal, description: `Deserializes a ${this.typeCref} into a new instance of ${this.thisCref}.` })); deserializerConstructor.add(function* () { const returnNow = new LocalVariable('returnNow', dotnet.Bool, { initializer: dotnet.False }); yield returnNow.declarationStatement; yield `${$this.beforeDeserialize.name}(${$this.contentParameter}, ref ${returnNow.value});`; yield If(returnNow, 'return;'); yield $this.deserializeStatements; 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: NewSchema): boolean { if (aSchema.type === SchemaType.Dictionary) { return true; } if (aSchema.type !== SchemaType.Object) { return false; } const objSchema = (<ObjectSchema>aSchema).parents?.immediate; if (!objSchema || objSchema.length === 0) { return false; } for (const parent of objSchema) { if (this.hasAadditionalProperties(parent)) { return true; } } return false; } get deserializeStatements() { const $this = this; return function* () { yield '// actually deserialize '; for (const virtualProperty of values(<Array<NewVirtualProperty>>$this.allVirtualProperties)) { // yield `// deserialize ${virtualProperty.name} from ${$this.serializationFormat}`; const isRequired = !!(virtualProperty.required && virtualProperty.read && virtualProperty.create && virtualProperty.update); const type = $this.resolver(<NewSchema>virtualProperty.property.schema, isRequired); const cvt = type.convertObjectMethod; const t = `((${virtualProperty.originalContainingSchema.language.csharp?.fullInternalInterfaceName})this)`; const tt = type ? `(${type.declaration})` : ''; yield If(`content.Contains("${getVirtualPropertyName(virtualProperty)}")`, `${t}.${getVirtualPropertyName(virtualProperty)} = ${tt} ${$this.contentParameter}.GetValueForProperty("${getVirtualPropertyName(virtualProperty)}",${t}.${getVirtualPropertyName(virtualProperty)}, ${cvt});`); } }; } protected addDeserializerMethod() { const $this = this; const deserialzeMethod = this.targetClass.addMethod(new Method(`DeserializeFrom${this.serializationFormat}`, this.targetInterface, { parameters: [this.contentParameter], static: Modifier.Static, description: `Deserializes a ${this.typeCref} into an instance of ${this.thisCref}.`, returnsDescription: `an instance of ${this.interfaceCref}.` })); deserialzeMethod.add(function* () { yield Return($this.targetClass.new($this.contentParameter)); }); } protected addPartialMethods() { const before = `BeforeDeserialize${this.serializationFormat}`; const after = `AfterDeserialize${this.serializationFormat}`; this.beforeDeserialize = this.targetClass.addMethod(new PartialMethod(before, dotnet.Void, { access: Access.Default, parameters: [this.contentParameter, this.returnNowParameter], description: `<c>${before}</c> will be called before the deserialization has commenced, allowing complete customization of the object before it is deserialized. If you wish to disable the default deserialization entirely, return <c>true</c> in the <paramref name="returnNow" /> output parameter. Implement this method in a partial class to enable this behavior.` })); this.afterDeserialize = this.targetClass.addMethod(new PartialMethod(after, dotnet.Void, { access: Access.Default, parameters: [this.contentParameter], description: `<c>${after}</c> will be called after the deserialization has finished, allowing customization of the object before it is returned. Implement this method in a partial class to enable this behavior ` })); } }