From 446663fe641dcb4fa9beb99f30ad4061c5a58544 Mon Sep 17 00:00:00 2001 From: Garrett Serack Date: Fri, 23 Mar 2018 11:25:31 -0700 Subject: [PATCH] WIP --- common/text-manipulation.ts | 10 + csharp/code-dom/access-modifier.ts | 1 + csharp/code-dom/class.ts | 6 +- csharp/code-dom/initializer.ts | 6 +- csharp/code-dom/method.ts | 25 ++ csharp/code-dom/mscorlib.ts | 7 +- csharp/code-dom/parameter-modifier.ts | 6 + csharp/code-dom/parameter.ts | 9 +- csharp/code-dom/statements/statement.ts | 41 ++- csharp/code-dom/type-declaration.ts | 1 - csharp/inferrer.ts | 1 - csharp/lowlevel-generator/clientruntime.ts | 15 +- csharp/lowlevel-generator/generator.ts | 5 - csharp/lowlevel-generator/main.ts | 3 +- csharp/lowlevel-generator/model/class.ts | 65 ++++- .../model/interface-property.ts | 2 +- csharp/lowlevel-generator/model/interface.ts | 22 +- csharp/lowlevel-generator/model/namespace.ts | 255 +++++++++++------- csharp/lowlevel-generator/model/property.ts | 18 +- .../lowlevel-generator/operation/api-class.ts | 6 +- csharp/lowlevel-generator/operation/method.ts | 134 +++++++-- .../lowlevel-generator/operation/parameter.ts | 46 +++- csharp/lowlevel-generator/primitives/Uuid.ts | 15 +- csharp/lowlevel-generator/primitives/array.ts | 15 +- .../lowlevel-generator/primitives/boolean.ts | 25 +- .../primitives/byte-array.ts | 15 +- csharp/lowlevel-generator/primitives/char.ts | 15 +- .../primitives/date-time.ts | 28 +- csharp/lowlevel-generator/primitives/date.ts | 15 +- .../lowlevel-generator/primitives/duration.ts | 15 +- .../primitives/floatingpoint.ts | 15 +- .../lowlevel-generator/primitives/integer.ts | 15 +- .../lowlevel-generator/primitives/stream.ts | 15 +- .../lowlevel-generator/primitives/string.ts | 42 +-- .../lowlevel-generator/primitives/wildcard.ts | 28 +- csharp/lowlevel-generator/private-data.ts | 10 + csharp/lowlevel-generator/project.ts | 10 +- csharp/lowlevel-generator/support/enum.ts | 16 +- .../support/json-serializer.ts | 101 +++++++ csharp/lowlevel-generator/type-declaration.ts | 36 +++ main.ts | 7 +- package.json | 2 +- remodeler/code-model.ts | 34 ++- remodeler/inferrer.ts | 28 ++ remodeler/remodeler.ts | 10 +- 45 files changed, 958 insertions(+), 238 deletions(-) create mode 100644 csharp/code-dom/parameter-modifier.ts create mode 100644 csharp/lowlevel-generator/private-data.ts create mode 100644 csharp/lowlevel-generator/support/json-serializer.ts create mode 100644 csharp/lowlevel-generator/type-declaration.ts create mode 100644 remodeler/inferrer.ts diff --git a/common/text-manipulation.ts b/common/text-manipulation.ts index 2f703a04ee..7ed479aa28 100644 --- a/common/text-manipulation.ts +++ b/common/text-manipulation.ts @@ -1,3 +1,4 @@ +import { Dictionary } from "#remodeler/common"; let indentation = " "; @@ -112,6 +113,15 @@ export function fixEOL(content: string) { return content.replace(/\r\n/g, EOL); } +export function map(dictionary: Dictionary, callbackfn: (key: string, value: T) => U, thisArg?: any): U[] { + return Object.getOwnPropertyNames(dictionary).map((key) => callbackfn(key, dictionary[key])); +} +export function selectMany(multiArray: T[][]): T[] { + const result = new Array(); + multiArray.map(v => result.push(...v)); + return result; +} + export function indent(content: string, factor: number = 1): string { const i = indentation.repeat(factor); content = i + fixEOL(content.trim()); diff --git a/csharp/code-dom/access-modifier.ts b/csharp/code-dom/access-modifier.ts index 840628a9d2..593eea2c81 100644 --- a/csharp/code-dom/access-modifier.ts +++ b/csharp/code-dom/access-modifier.ts @@ -5,6 +5,7 @@ export enum AccessModifier { ProtectedInternal = "protected internal", PrivateProtected = "private protected", Private = "private", + Default = "", } const order = [AccessModifier.Public, AccessModifier.Internal, AccessModifier.Protected, AccessModifier.ProtectedInternal, AccessModifier.PrivateProtected, AccessModifier.Private]; diff --git a/csharp/code-dom/class.ts b/csharp/code-dom/class.ts index 0d2cddac9f..ad1bc67402 100644 --- a/csharp/code-dom/class.ts +++ b/csharp/code-dom/class.ts @@ -8,6 +8,8 @@ import { Namespace } from "./namespace"; export class Class extends Type { protected classOrStruct: "class" | "struct" = "class"; + public isStatic: boolean = false; + protected fields = new Array(); public partial: boolean = false; @@ -25,9 +27,11 @@ export class Class extends Type { const implementsInterfaces = this.interfaces.map(v => v.fullName).join(', '); const description = comment(this.description, docCommentPrefix); const partial = this.partial ? "partial " : ""; + const stat = this.isStatic ? "static " : ""; + return ` ${description} -${this.accessModifier} ${partial}${this.classOrStruct} ${this.name}${colon}${extendsClass}${comma}${implementsInterfaces} +${this.accessModifier} ${stat}${partial}${this.classOrStruct} ${this.name}${colon}${extendsClass}${comma}${implementsInterfaces} `.trim(); } diff --git a/csharp/code-dom/initializer.ts b/csharp/code-dom/initializer.ts index 05f4d26127..1f0e64a21d 100644 --- a/csharp/code-dom/initializer.ts +++ b/csharp/code-dom/initializer.ts @@ -3,9 +3,9 @@ export class Initializer { protected apply(initializer?: Partial) { if (initializer) { for (const i in (initializer)) { - if ((initializer)[i]) { - (this)[i] = (initializer)[i] - }; + //if ((initializer)[i]) { + (this)[i] = (initializer)[i] + //}; } } } diff --git a/csharp/code-dom/method.ts b/csharp/code-dom/method.ts index f7429c6dd9..64003e8f45 100644 --- a/csharp/code-dom/method.ts +++ b/csharp/code-dom/method.ts @@ -54,4 +54,29 @@ ${indent(super.implementation)} }`.trim(); } +} + +export class PartialMethod extends Method { + constructor(name: string, returnType: TypeDeclaration = mscorlib.Void, objectIntializer?: Partial) { + super(name, returnType); + this.apply(objectIntializer); + } + + public get declaration(): string { + const parameterDeclaration = this.parameters.joinWith(p => p.declaration, CommaChar); + const stat = this.isStatic ? "static " : ""; + const asynch = this.isAsync ? "async " : ""; + return ` +${this.summaryDocumentation} +${this.parameterDocumentation} +partial ${stat}${asynch}${this.returnType.use} ${this.name}(${parameterDeclaration}) +`.trim(); + } + + public get implementation(): string { + return ` +${this.declaration};`.trim(); + + } + } \ No newline at end of file diff --git a/csharp/code-dom/mscorlib.ts b/csharp/code-dom/mscorlib.ts index 494a8d8ba8..9c4f56c44c 100644 --- a/csharp/code-dom/mscorlib.ts +++ b/csharp/code-dom/mscorlib.ts @@ -8,10 +8,6 @@ export class LibraryType implements TypeDeclaration { return `${this.fullName}`; } - public validation(propertyName: string): string { - return ''; - } - public get implementation(): string { return ``; } @@ -30,8 +26,7 @@ export const Duration: TypeDeclaration = new LibraryType("TimeSpan"); export const Binary: TypeDeclaration = new LibraryType("byte[]"); export const Bool: TypeDeclaration = new LibraryType("bool"); export const Object: TypeDeclaration = new LibraryType("object"); - +export const ThisObject: TypeDeclaration = new LibraryType("this object"); export const Task: TypeDeclaration = new LibraryType("System.Threading.Tasks.Task"); export const CancellationToken: TypeDeclaration = new LibraryType("System.Threading.CancellationToken"); -export const EventListener: TypeDeclaration = new LibraryType("Microsoft.Rest.EventListener"); \ No newline at end of file diff --git a/csharp/code-dom/parameter-modifier.ts b/csharp/code-dom/parameter-modifier.ts new file mode 100644 index 0000000000..cfcc079e26 --- /dev/null +++ b/csharp/code-dom/parameter-modifier.ts @@ -0,0 +1,6 @@ +export enum ParameterModifier { + None = "", + Ref = "ref", + Out = "out", + In = "in", +} diff --git a/csharp/code-dom/parameter.ts b/csharp/code-dom/parameter.ts index ff6b605700..4cd495b7a2 100644 --- a/csharp/code-dom/parameter.ts +++ b/csharp/code-dom/parameter.ts @@ -1,11 +1,16 @@ import { TypeDeclaration } from "./type-declaration"; import { Expression } from "#csharp/code-dom/expression"; import { Initializer } from "#csharp/code-dom/initializer"; +import { ParameterModifier } from "#csharp/code-dom/parameter-modifier"; export class Parameter extends Initializer implements Expression { public description: string = ""; + public genericParameters = new Array(); + public where?: string; + public modifier: ParameterModifier = ParameterModifier.None; + public defaultInitializer?: string; - public constructor(private name: string, private type: TypeDeclaration, public genericParameters = new Array(), public where?: string, objectInitializer?: Partial) { + public constructor(public name: string, public type: TypeDeclaration, objectInitializer?: Partial) { super(); this.apply(objectInitializer); } @@ -14,7 +19,7 @@ export class Parameter extends Initializer implements Expression { return ` ${this.description} `; } public get declaration(): string { - return `${this.type.use} ${this.name}`; + return `${this.modifier} ${this.type.use} ${this.name} ${this.defaultInitializer || ''}`.trim(); } public get use(): string { return this.name; diff --git a/csharp/code-dom/statements/statement.ts b/csharp/code-dom/statements/statement.ts index b31fce7226..df68d28818 100644 --- a/csharp/code-dom/statements/statement.ts +++ b/csharp/code-dom/statements/statement.ts @@ -3,6 +3,7 @@ import { LiteralStatement } from "#csharp/code-dom/statements/literal"; import { Initializer } from "../initializer"; export type OneOrMoreStatements = string | (() => Iterable) | Iterable | Statement; +export type ManyStatements = OneOrMoreStatements | (() => Iterable); export interface Statement { implementation: string; @@ -22,11 +23,21 @@ export class Statements extends Initializer implements Statement { this.apply(objectIntializer); } - public add(statements: OneOrMoreStatements): Statements { + public get count(): number { + return this.statements.length + } + + public add(statements: ManyStatements): Statements { if (typeof statements === 'function') { - statements = statements(); + // console.error(statements); + for (const each of statements()) { + this.aadd(each); + } + //statements = statements(); + return this; } - if (typeof statements === 'string') { + return this.aadd(statements); + /*if (typeof statements === 'string') { statements = new LiteralStatement(statements); } if (typeof statements === 'object') { @@ -38,6 +49,30 @@ export class Statements extends Initializer implements Statement { } } } + return this;*/ + } + private aadd(statements: OneOrMoreStatements): Statements { + if (typeof statements === 'function') { + console.error(statements); + statements = statements(); + } + if (typeof statements === 'string') { + this.statements.push(new LiteralStatement(statements)); + return this; + } + if (typeof statements === 'object') { + console.error(statements); + if (isStatement(statements)) { + this.statements.push(statements); + } else if (statements instanceof Statements) { + this.statements.push(...statements.statements) + } + else { + for (const statement of statements) { + this.statements.push(typeof statement === 'string' ? new LiteralStatement(statement) : statement); + } + } + } return this; } diff --git a/csharp/code-dom/type-declaration.ts b/csharp/code-dom/type-declaration.ts index 8cd9583e34..0573533ee7 100644 --- a/csharp/code-dom/type-declaration.ts +++ b/csharp/code-dom/type-declaration.ts @@ -2,5 +2,4 @@ export interface TypeDeclaration { implementation: string; use: string; - validation(propertyName: string): string; } diff --git a/csharp/inferrer.ts b/csharp/inferrer.ts index a3c2bf822f..1e6c972d3c 100644 --- a/csharp/inferrer.ts +++ b/csharp/inferrer.ts @@ -3,7 +3,6 @@ import { deserialize, serialize } from "#common/yaml"; import { processCodeModel } from "#common/process-code-model"; import { ModelState } from "#common/model-state"; import { Model } from "remodeler/code-model"; -import { deconstruct, fixLeadingNumber, pascalCase, camelCase } from "#common/text-manipulation"; export async function process(service: Host) { return await processCodeModel(inferStuff, service); diff --git a/csharp/lowlevel-generator/clientruntime.ts b/csharp/lowlevel-generator/clientruntime.ts index 59aa138a16..4c96dbe990 100644 --- a/csharp/lowlevel-generator/clientruntime.ts +++ b/csharp/lowlevel-generator/clientruntime.ts @@ -1,4 +1,17 @@ import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; import { LibraryType } from "#csharp/code-dom/mscorlib"; +import { Namespace } from "#csharp/code-dom/namespace"; +import { Interface } from "#csharp/code-dom/interface"; + +export const ClientRuntime: Namespace = new Namespace("Microsoft.Rest.ClientRuntime"); + +export const ISendAsync: Interface = new Interface(ClientRuntime, "ISendAsync"); +export const IJsonSerializable: Interface = new Interface(ClientRuntime, "IJsonSerializable"); +export const IXmlSerializable: Interface = new Interface(ClientRuntime, "IXmlSerializable"); + +export const EventListener: TypeDeclaration = new LibraryType(`${ClientRuntime.fullName}.EventListener`); +export const IValidates: Interface = new Interface(ClientRuntime, "IValidates"); + +export const JsonNode: TypeDeclaration = new LibraryType(`Carbon.Json.JsonNode`); +export const JsonObject: TypeDeclaration = new LibraryType(`Carbon.Json.JsonObject`); -export const ISendAsync: TypeDeclaration = new LibraryType("Microsoft.Rest.ISendAsync"); \ No newline at end of file diff --git a/csharp/lowlevel-generator/generator.ts b/csharp/lowlevel-generator/generator.ts index d4269445ce..1184d504dd 100644 --- a/csharp/lowlevel-generator/generator.ts +++ b/csharp/lowlevel-generator/generator.ts @@ -6,11 +6,6 @@ import { Model, Schema } from "remodeler/code-model"; import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; import { Project } from "./project"; - -export interface PrivateData { - -} - export class State extends ModelState { public get project(): Project { diff --git a/csharp/lowlevel-generator/main.ts b/csharp/lowlevel-generator/main.ts index 83abce12f4..187ff97d87 100644 --- a/csharp/lowlevel-generator/main.ts +++ b/csharp/lowlevel-generator/main.ts @@ -17,7 +17,7 @@ import { deserialize } from "#common/yaml"; // model constructors? // - allOf creation? -// enum generator + // validations // serialization/deserialization/polymorphic deserializer/shape deserializer? // url construction @@ -29,7 +29,6 @@ import { deserialize } from "#common/yaml"; // client runtime -// later: refactor create/async to be consistent export async function process(service: Host) { try { diff --git a/csharp/lowlevel-generator/model/class.ts b/csharp/lowlevel-generator/model/class.ts index 6b31687781..b7c3f2dae7 100644 --- a/csharp/lowlevel-generator/model/class.ts +++ b/csharp/lowlevel-generator/model/class.ts @@ -12,21 +12,35 @@ import { Interface } from "#csharp/code-dom/interface"; import { ProxyProperty } from "./proxy-property"; import { Field } from "#csharp/code-dom/field"; import { AccessModifier } from "#csharp/code-dom/access-modifier"; +import { IJsonSerializable, EventListener, IValidates } from "#csharp/lowlevel-generator/clientruntime"; +import { Statements, OneOrMoreStatements } from "#csharp/code-dom/statements/statement"; +import { PrivateData } from "#csharp/lowlevel-generator/private-data"; +import { EOL } from "#common/text-manipulation"; +import { Method } from "#csharp/code-dom/method"; +import { Parameter } from "#csharp/code-dom/parameter"; export class ModelClass extends Class { + public serializeStatements = new Statements(); + private validateMethod?: Method; + constructor(namespace: Namespace, schema: Schema, state: State, objectInitializer?: Partial) { super(namespace, schema.details.name); this.apply(objectInitializer); + const privateData: PrivateData = schema.details.privateData; + // mark the code-model with the class we're creating. - schema.details.privateData["class-implementation"] = this; + privateData.classImplementation = this; // track the namespace we've used. schema.details.namespace = namespace.fullName; + // mark it as json serializable + this.interfaces.push(IJsonSerializable); + // create an interface for this model class - const modelInterface = schema.details.privateData["interface-implementation"] || new ModelInterface(namespace, schema, state); + const modelInterface = privateData.interfaceImplementation || new ModelInterface(namespace, schema, this, state); this.interfaces.push(modelInterface); // handle s @@ -35,10 +49,10 @@ export class ModelClass extends Class { const aSchema = schema.allOf[allOf]; const aState = state.path("allOf"); - const td = state.project.modelsNamespace.resolveTypeDeclaration(aSchema, aState); + const td = state.project.modelsNamespace.resolveTypeDeclaration(aSchema, true, aState); // add the interface as a parent to our interface. - const iface: ModelInterface = aSchema.details.privateData["interface-implementation"]; + const iface: ModelInterface = aSchema.details.privateData.interfaceImplementation; modelInterface.interfaces.push(iface); @@ -47,6 +61,8 @@ export class ModelClass extends Class { // now, create proxy properties for the members iface.allProperties.map(each => this.addProperty(new ProxyProperty(backingField, each, state))); + + this.serializeStatements.add(`${backingField.value}?.ToJson(result);`) } // generate a protected backing field for each // and then expand the nested properties into this class forwarding to the member. @@ -56,7 +72,9 @@ export class ModelClass extends Class { for (const propertyName in schema.properties) { const property = schema.properties[propertyName]; - this.addProperty(new ModelProperty(this, property, state.path('properties', propertyName))); + const prop = this.addProperty(new ModelProperty(this, property, state.path('properties', propertyName))); + + this.serializeStatements.add(`result.Add("${propertyName}",${prop.name}.ToJson());`); } if (schema.additionalProperties) { @@ -69,6 +87,43 @@ export class ModelClass extends Class { } } + // add validation function + const statements = new Statements(); + this.properties.map(each => statements.add((each).validatePresenceStatement)); + this.properties.map(each => statements.add((each).validationStatement)); + + // const propVal = this.properties.joinWith(each => (each).validationStatement, EOL); + // const propValPresence = this.properties.joinWith(each => (each).validatePresenceStatement, EOL); + if (statements.count > 0) { + // we do have something to valdiate! + + // add the IValidates implementation to this object. + this.interfaces.push(IValidates); + this.validateMethod = this.addMethod(new Method("Validate", mscorlib.Task, { + parameters: [new Parameter("listener", EventListener)], + isAsync: true, + })); + this.validateMethod.add(statements); + } + + } + + validatePresence(propertyName: string): OneOrMoreStatements { + return `await listener.AssertNotNull(nameof(${propertyName}),${propertyName});`.trim(); + } + + validateValue(propertyName: string): OneOrMoreStatements { + //if (this.validateMethod) { + //return `${propertyName}.Validate(listener);` + //} + // return `(${propertyName} as Microsoft.Rest.ClientRuntime.IValidates)?.Validate(listener);`; + return `await listener.AssertObjectIsValid(nameof(${propertyName}), ${propertyName});`; + } + jsonserialize(propertyName: string): OneOrMoreStatements { + return `/* serialize json object here */`; + } + jsondeserialize(propertyName: string): OneOrMoreStatements { + return `/* deserialize json object here */`; } } \ No newline at end of file diff --git a/csharp/lowlevel-generator/model/interface-property.ts b/csharp/lowlevel-generator/model/interface-property.ts index f6a660adea..9db129e4c6 100644 --- a/csharp/lowlevel-generator/model/interface-property.ts +++ b/csharp/lowlevel-generator/model/interface-property.ts @@ -9,7 +9,7 @@ import { Interface } from "#csharp/code-dom/interface"; export class ModelInterfaceProperty extends InterfaceProperty { constructor(parent: ModelInterface, property: codeModel.PropertyReference, state: State, objectInitializer?: Partial) { - super(property.details.name, state.project.modelsNamespace.resolveTypeDeclaration(property.schema, state.path("schema"))); + super(property.details.name, state.project.modelsNamespace.resolveTypeDeclaration(property.schema, property.details.required, state.path("schema"))); this.apply(objectInitializer); } } \ No newline at end of file diff --git a/csharp/lowlevel-generator/model/interface.ts b/csharp/lowlevel-generator/model/interface.ts index 7ebe68a7aa..74d5f4efa9 100644 --- a/csharp/lowlevel-generator/model/interface.ts +++ b/csharp/lowlevel-generator/model/interface.ts @@ -3,13 +3,29 @@ import { Schema } from "#remodeler/code-model"; import { Namespace } from "#csharp/code-dom/namespace"; import { Interface } from "#csharp/code-dom/interface"; import { ModelInterfaceProperty } from "./interface-property"; +import { TypeDeclaration } from "#csharp/lowlevel-generator/type-declaration"; +import { ModelClass } from "#csharp/lowlevel-generator/model/class"; +import { OneOrMoreStatements } from "#csharp/code-dom/statements/statement"; -export class ModelInterface extends Interface { - constructor(parent: Namespace, schema: Schema, state: State, objectInitializer?: Partial) { +export class ModelInterface extends Interface implements TypeDeclaration { + validatePresence(propertyName: string): OneOrMoreStatements { + return this.classImplementation.validatePresence(propertyName); + } + validateValue(propertyName: string): OneOrMoreStatements { + return this.classImplementation.validateValue(propertyName); + } + jsonserialize(propertyName: string): OneOrMoreStatements { + return this.classImplementation.jsonserialize(propertyName); + } + jsondeserialize(propertyName: string): OneOrMoreStatements { + return this.classImplementation.jsondeserialize(propertyName); + } + + constructor(parent: Namespace, schema: Schema, public classImplementation: ModelClass, state: State, objectInitializer?: Partial) { super(parent, `I${schema.details.name}`); this.apply(objectInitializer); - schema.details.privateData["interface-implementation"] = this; + schema.details.privateData.interfaceImplementation = this; for (const propertyName in schema.properties) { this.addProperty(new ModelInterfaceProperty(this, schema.properties[propertyName], state.path('properties', propertyName))); diff --git a/csharp/lowlevel-generator/model/namespace.ts b/csharp/lowlevel-generator/model/namespace.ts index 34b507f6a0..18cda6cd35 100644 --- a/csharp/lowlevel-generator/model/namespace.ts +++ b/csharp/lowlevel-generator/model/namespace.ts @@ -8,13 +8,13 @@ import { Schema, JsonType } from "#remodeler/code-model"; import { ModelClass } from "./class"; import { StringFormat } from "#remodeler/known-format"; import { hasProperties } from "#common/text-manipulation"; -import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; +import { TypeDeclaration } from "../type-declaration"; import { getKnownFormatType } from "#remodeler/interpretations"; import { Wildcard, UntypedWildcard } from "../primitives/wildcard" import { EnumClass } from "../support/enum"; import { ByteArray } from "../primitives/byte-array"; -import { Boolean } from "../primitives/boolean"; +import { Boolean, NullableBoolean } from "../primitives/boolean"; import { Float } from "../primitives/floatingpoint"; import { ArrayOf } from "../primitives/array"; import { Integer } from "../primitives/integer"; @@ -22,8 +22,10 @@ import { Date } from "../primitives/date"; import { DateTime, DateTime1123 } from "../primitives/date-time"; import { Duration } from "../primitives/duration"; import { Uuid } from "../primitives/Uuid"; -import { String } from "../primitives/string"; +import { String, NullableString } from "../primitives/string"; import { Char } from "../primitives/char"; +import { PrivateData } from "#csharp/lowlevel-generator/private-data"; +import { ModelInterface } from "#csharp/lowlevel-generator/model/interface"; export class ModelsNamespace extends Namespace { @@ -43,21 +45,21 @@ export class ModelsNamespace extends Namespace { if (validation.objectWithFormat(schema, state)) { continue; } - this.resolveTypeDeclaration(schema, state); + this.resolveTypeDeclaration(schema, true, state); } - - } private static INVALID = null; - public resolveTypeDeclaration(schema: Schema | undefined, state: State): TypeDeclaration { + public resolveTypeDeclaration(schema: Schema | undefined, required: boolean, state: State): TypeDeclaration { if (!schema) { throw new Error("SCHEMA MISSING?") } + const privateData: PrivateData = schema.details.privateData; + // have we done this object already? - if (schema.details.privateData["type-declaration"]) { - return schema.details.privateData["type-declaration"]; + if (privateData.typeDeclaration) { + return privateData.typeDeclaration; } // determine if we need a new model class for the type or just a known type object @@ -67,12 +69,126 @@ export class ModelsNamespace extends Namespace { if (schema.additionalProperties && !hasProperties(schema.properties)) { if (schema.additionalProperties === true) { // the object is a wildcard for all key/object-value pairs - return this.createWildcardObject(schema, state); + return privateData.typeDeclaration = new UntypedWildcard(); + } else { + // the object is a wildcard for all key/-value pairs + const wcSchema = this.resolveTypeDeclaration(schema.additionalProperties, false, state.path("additionalProperties")); + + return privateData.typeDeclaration = new Wildcard(wcSchema); + } + } + + // otherwise, if it has additionalProperties + // it's a regular object, that has a catch-all for unspecified properties. + // (handled in ModelClass itself) + const mc = privateData.classImplementation || new ModelClass(this, schema, this.state); + return privateData.typeDeclaration = privateData.interfaceImplementation; + + case JsonType.String: + switch (schema.format) { + case StringFormat.Base64Url: + case StringFormat.Byte: + // member should be byte array + // on wire format should be base64url + return privateData.typeDeclaration = new ByteArray(); + + case StringFormat.Binary: + // represent as a stream + // wire format is stream of bytes + throw new Error("Method not implemented."); + + case StringFormat.Char: + // a single character + return privateData.typeDeclaration = new Char(schema.enum.length > 0 ? schema.enum : undefined); + + case StringFormat.Date: + return privateData.typeDeclaration = new Date(); + + case StringFormat.DateTime: + return privateData.typeDeclaration = new DateTime(); + + case StringFormat.DateTimeRfc1123: + return privateData.typeDeclaration = new DateTime1123(); + + case StringFormat.Duration: + return privateData.typeDeclaration = new Duration(); + + case StringFormat.Uuid: + return privateData.typeDeclaration = new Uuid(); + + case StringFormat.Password: + case StringFormat.None: + case undefined: + case null: + if (schema.extensions["x-ms-enum"]) { + // this value is an enum type instead of a plain string. + const ec = state.project.supportNamespace.findClassByName(schema.extensions["x-ms-enum"].name); + if (ec.length > 0) { + return privateData.typeDeclaration = ec[0]; + } + return privateData.typeDeclaration = new EnumClass(schema, state); + } + + // just a regular old string. + return privateData.typeDeclaration = required ? new String(schema.minLength, schema.maxLength, schema.pattern, schema.enum.length > 0 ? schema.enum : undefined) : new NullableString(schema.minLength, schema.maxLength, schema.pattern, schema.enum.length > 0 ? schema.enum : undefined); + + default: + state.error(`Schema with type:'${schema.type} and 'format:'${schema.format}' is not recognized.`, message.DoesNotSupportEnum); + } + break; + + case JsonType.Boolean: + return privateData.typeDeclaration = required ? new Boolean() : new NullableBoolean(); + + case JsonType.Integer: + return privateData.typeDeclaration = new Integer(); + + case JsonType.Number: + return privateData.typeDeclaration = new Float(); + + case JsonType.Array: + const aSchema = this.resolveTypeDeclaration(schema.items, true, state.path("items")); + return privateData.typeDeclaration = new ArrayOf(aSchema); + + case undefined: + console.error(`schema 'undefined': ${schema.details.name} `); + // "any" case + // this can happen when a model is just an all-of something else. (sub in the other type?) + break; + + default: + this.state.error(`Schema is declared with invalid type '${schema.type}'`, message.UnknownJsonType); + return ModelsNamespace.INVALID; + } + return ModelsNamespace.INVALID; + } +} + +/* + Note: removed validation from above -- the validation should be in a separate step before we get into the cs* extensions. + +public resolveTypeDeclaration(schema: Schema | undefined, required: boolean, state: State): TypeDeclaration { + if (!schema) { + throw new Error("SCHEMA MISSING?") + } + // have we done this object already? + if (privateData.typeDeclaration) { + return privateData.typeDeclaration; + } + + // determine if we need a new model class for the type or just a known type object + switch (schema.type) { + case JsonType.Object: + // for certain, this should be a class of some sort. + if (schema.additionalProperties && !hasProperties(schema.properties)) { + if (schema.additionalProperties === true) { + // the object is a wildcard for all key/object-value pairs + return privateData.typeDeclaration = new UntypedWildcard(); } else { // the object is a wildcard for all key/-value pairs - const wcSchema = this.resolveTypeDeclaration(schema.additionalProperties, state.path("additionalProperties")); + const wcSchema = this.resolveTypeDeclaration(schema.additionalProperties, false, state.path("additionalProperties")); - return this.createWildcardForSchema(schema, wcSchema, state); + return privateData.typeDeclaration = new Wildcard(wcSchema); } } @@ -80,9 +196,9 @@ export class ModelsNamespace extends Namespace { // it's a regular object, that has a catch-all for unspecified properties. // (handled in ModelClass itself) - const mc = schema.details.privateData["class-implementation"] || new ModelClass(this, schema, this.state); - schema.details.privateData["type-declaration"] = schema.details.privateData["interface-implementation"]; - return schema.details.privateData["type-declaration"]; + const mc = privateData.classImplementation || new ModelClass(this, schema, this.state); + privateData.typeDeclaration = privateData.interfaceInplementation; + return privateData.typeDeclaration; case JsonType.String: switch (schema.format) { @@ -92,64 +208,69 @@ export class ModelsNamespace extends Namespace { return ModelsNamespace.INVALID; } // member should be byte array - // on wire format should be base64url - return this.createByteArray(schema, state); + // on wire format should be base64url + return privateData.typeDeclaration = new ByteArray(); case StringFormat.Binary: if (validation.schemaHasEnum(schema, state)) { return ModelsNamespace.INVALID; } - // represent as a stream + // represent as a stream // wire format is stream of bytes - return this.createStream(schema, state); + throw new Error("Method not implemented."); case StringFormat.Char: // a single character if (validation.hasXmsEnum(schema, state)) { return ModelsNamespace.INVALID; } - return this.createChar(schema, state); + return privateData.typeDeclaration = new Char(schema.enum.length > 0 ? schema.enum : undefined); case StringFormat.Date: if (validation.schemaHasEnum(schema, state)) { return ModelsNamespace.INVALID; } - return this.createDate(schema, state); + return privateData.typeDeclaration = new Date(); + case StringFormat.DateTime: if (validation.schemaHasEnum(schema, state)) { return ModelsNamespace.INVALID; } - return this.createDateTime(schema, state); + return privateData.typeDeclaration = new DateTime(); case StringFormat.DateTimeRfc1123: if (validation.schemaHasEnum(schema, state)) { return ModelsNamespace.INVALID; } - return this.createDateTime1123(schema, state); + return privateData.typeDeclaration = new DateTime1123(); case StringFormat.Duration: if (validation.schemaHasEnum(schema, state)) { return ModelsNamespace.INVALID; } - return this.createDuration(schema, state); + return privateData.typeDeclaration = new Duration(); case StringFormat.Uuid: if (validation.hasXmsEnum(schema, state)) { return ModelsNamespace.INVALID; } - return this.createUuid(schema, state); + return privateData.typeDeclaration = new Uuid(); case StringFormat.Password: case StringFormat.None: case undefined: case null: if (schema.extensions["x-ms-enum"]) { - // this value is an enum type instead of a plain string. - return this.createEnum(schema, state); + // this value is an enum type instead of a plain string. + const ec = state.project.supportNamespace.findClassByName(schema.extensions["x-ms-enum"].name); + if (ec.length > 0) { + return privateData.typeDeclaration = ec[0]; + } + return privateData.typeDeclaration = new EnumClass(schema, state); } // just a regular old string. - return this.createString(schema, state); + return privateData.typeDeclaration = new String(schema.minLength, schema.maxLength, schema.pattern, schema.enum.length > 0 ? schema.enum : undefined); default: state.error(`Schema with type:'${schema.type} and 'format:'${schema.format}' is not recognized.`, message.DoesNotSupportEnum); @@ -160,19 +281,19 @@ export class ModelsNamespace extends Namespace { if (validation.hasXmsEnum(schema, state)) { return ModelsNamespace.INVALID; } - return this.createBoolean(schema, state); + return privateData.typeDeclaration = required ? new Boolean() : new NullableBoolean(); case JsonType.Integer: if (validation.hasXmsEnum(schema, state)) { return ModelsNamespace.INVALID; } - return this.createInteger(schema, state); + return privateData.typeDeclaration = new Integer(); case JsonType.Number: if (validation.hasXmsEnum(schema, state)) { return ModelsNamespace.INVALID; } - return this.createFloatingpoint(schema, state); + return privateData.typeDeclaration = new Float(); case JsonType.Array: if (validation.hasXmsEnum(schema, state)) { @@ -181,12 +302,13 @@ export class ModelsNamespace extends Namespace { if (validation.arrayMissingItems(schema, state)) { return ModelsNamespace.INVALID; } - const aSchema = this.resolveTypeDeclaration(schema.items, state.path("items")); - return this.createArray(schema, aSchema, state); + const aSchema = this.resolveTypeDeclaration(schema.items, true, state.path("items")); + return privateData.typeDeclaration = new ArrayOf(aSchema); case undefined: console.error(`schema 'undefined': ${schema.details.name} `); // "any" case + // this can happen when a model is just an all-of something else. (sub in the other type?) break; default: @@ -196,64 +318,17 @@ export class ModelsNamespace extends Namespace { return ModelsNamespace.INVALID; } - createWildcardObject(schema: Schema, state: State): TypeDeclaration { - return schema.details.privateData["type-declaration"] = new UntypedWildcard(); - } - createWildcardForSchema(schema: Schema, typeDecl: TypeDeclaration, state: State): TypeDeclaration { - return schema.details.privateData["type-declaration"] = new Wildcard(typeDecl); - } - createByteArray(schema: Schema, state: State): TypeDeclaration { - return schema.details.privateData["type-declaration"] = new ByteArray(); - } - createEnum(schema: Schema, state: State): TypeDeclaration { - // throw new Error("Method not implemented."); - // this schema has an x-ms-enum. Need to create the enum class definition - // first check for an enumclass with the same name - const ec = state.project.supportNamespace.findClassByName(schema.extensions["x-ms-enum"].name); - if (ec.length > 0) { - return schema.details.privateData["type-declaration"] = ec[0]; - } - return schema.details.privateData["type-declaration"] = new EnumClass(schema, state); - } - createStream(schema: Schema, state: State): TypeDeclaration { - throw new Error("Method not implemented."); - } - createBoolean(schema: Schema, state: State): TypeDeclaration { - return schema.details.privateData["type-declaration"] = new Boolean(); - } - createFloatingpoint(schema: Schema, state: State): TypeDeclaration { - return schema.details.privateData["type-declaration"] = new Float(); - } - createArray(schema: Schema, typeDecl: TypeDeclaration, state: State): TypeDeclaration { - return schema.details.privateData["type-declaration"] = new ArrayOf(typeDecl); - } - createInteger(schema: Schema, state: State): TypeDeclaration { - return schema.details.privateData["type-declaration"] = new Integer(); - } - createDate(schema: Schema, state: State): TypeDeclaration { - return schema.details.privateData["type-declaration"] = new Date(); - } - createDateTime(schema: Schema, state: State): TypeDeclaration { - return schema.details.privateData["type-declaration"] = new DateTime(); - } - createDateTime1123(schema: Schema, state: State): TypeDeclaration { - return schema.details.privateData["type-declaration"] = new DateTime1123(); - } - createDuration(schema: Schema, state: State): TypeDeclaration { - return schema.details.privateData["type-declaration"] = new Duration(); - } - createUuid(schema: Schema, state: State): TypeDeclaration { - return schema.details.privateData["type-declaration"] = new Uuid(); - } - createString(schema: Schema, state: State): TypeDeclaration { - // create a validated string typedeclaration. - return schema.details.privateData["type-declaration"] = new String(schema.minLength, schema.maxLength, schema.pattern, schema.enum.length > 0 ? schema.enum : undefined); - } - createChar(schema: Schema, state: State): TypeDeclaration { - // create a validated string typedeclaration. - return schema.details.privateData["type-declaration"] = new Char(schema.enum.length > 0 ? schema.enum : undefined); - } + + + + + } + + + + */ + diff --git a/csharp/lowlevel-generator/model/property.ts b/csharp/lowlevel-generator/model/property.ts index f90e17deed..c47a6a22e1 100644 --- a/csharp/lowlevel-generator/model/property.ts +++ b/csharp/lowlevel-generator/model/property.ts @@ -1,15 +1,29 @@ import { Property } from "#csharp/code-dom/property"; -import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; +import { TypeDeclaration, LibraryType } from "../type-declaration"; import { ModelClass } from "./class"; import { ModelInterface } from "./interface"; import * as codeModel from "#remodeler/code-model"; import { State } from "../generator"; import { ToDo } from "#csharp/code-dom/mscorlib"; import { Interface } from "#csharp/code-dom/interface"; +import { OneOrMoreStatements } from "#csharp/code-dom/statements/statement"; export class ModelProperty extends Property { constructor(parent: ModelClass, property: codeModel.PropertyReference, state: State, objectInitializer?: Partial) { - super(property.details.name, state.project.modelsNamespace.resolveTypeDeclaration(property.schema, state.path("schema"))); + super(property.details.name, state.project.modelsNamespace.resolveTypeDeclaration(property.schema, property.details.required, state.path("schema"))); this.apply(objectInitializer); } + + public get validatePresenceStatement(): OneOrMoreStatements { + return ``; + } + public get validationStatement(): OneOrMoreStatements { + return (this.type).validateValue(this.name); + } + public get jsonSerializationStatement(): OneOrMoreStatements { + return (this.type).jsonserialize(this.name); + } + public get jsonDeserializationStatement(): OneOrMoreStatements { + return (this.type).jsondeserialize(this.name); + } } diff --git a/csharp/lowlevel-generator/operation/api-class.ts b/csharp/lowlevel-generator/operation/api-class.ts index 087c55a9c1..6797d653bd 100644 --- a/csharp/lowlevel-generator/operation/api-class.ts +++ b/csharp/lowlevel-generator/operation/api-class.ts @@ -15,14 +15,14 @@ import { ISendAsync } from "../clientruntime"; export class ApiClass extends Class { protected sender: Property; - constructor(protected project: Project, protected state: State, objectInitializer?: Partial) { - super(project.serviceNamespace, state.model.details.name); + constructor(namespace: Namespace, protected state: State, objectInitializer?: Partial) { + super(namespace, state.model.details.name); this.apply(objectInitializer); // add basics this.sender = this.addProperty(new Property("Sender", ISendAsync)); // remember the namespace for this class. - state.model.details.namespace = project.serviceNamespace.fullName; + state.model.details.namespace = namespace.fullName; // add operations from code model for (const operationName in state.model.components.operations) { diff --git a/csharp/lowlevel-generator/operation/method.ts b/csharp/lowlevel-generator/operation/method.ts index c81fd4aa56..d0c03457d6 100644 --- a/csharp/lowlevel-generator/operation/method.ts +++ b/csharp/lowlevel-generator/operation/method.ts @@ -3,11 +3,11 @@ import * as codemodel from "#remodeler/code-model"; import { Method } from "#csharp/code-dom/method"; import * as mscorlib from "#csharp/code-dom/mscorlib"; import { Parameter } from "#csharp/code-dom/parameter"; -import { OperationParameter, OperationBodyParameter, CallbackParameter } from "../operation/parameter"; +import { OperationParameter, OperationBodyParameter, CallbackParameter, undefinedType } from "../operation/parameter"; import { Model } from "#remodeler/code-model"; import * as message from "../messages"; -import { all, EOL } from "#common/text-manipulation"; +import { all, EOL, camelCase, fixLeadingNumber, deconstruct, map, selectMany } from "#common/text-manipulation"; import { State } from "../generator"; import { Statement, Statements, OneOrMoreStatements } from "#csharp/code-dom/statements/statement"; import { Expression } from "#csharp/code-dom/expression"; @@ -16,6 +16,7 @@ import { Try } from "#csharp/code-dom/statements/try"; import { Finally } from "#csharp/code-dom/statements/finally"; import { Switch } from "#csharp/code-dom/statements/switch"; import { Case, DefaultCase, TerminalDefaultCase } from "#csharp/code-dom/statements/case"; +import { ClientRuntime, EventListener as EventListenr } from "#csharp/lowlevel-generator/clientruntime"; export class OperationMethod extends Method { constructor(protected parent: Class, protected operation: codemodel.HttpOperation, protected state: State, objectInitializer?: Partial) { @@ -29,30 +30,90 @@ export class OperationMethod extends Method { this.description = this.operation.details.description || ""; // add body paramter + let bodyParameter: OperationBodyParameter; + if (this.operation.requestBody) { const appjson = this.operation.requestBody.content["application/json"]; if (appjson && appjson.schema) { - this.addParameter(new OperationBodyParameter(this, "body", this.operation.requestBody.description || "", appjson.schema, this.state.path('requestBody'))); + + bodyParameter = new OperationBodyParameter(this, "body", this.operation.requestBody.description || "", appjson.schema, this.operation.requestBody.required, this.state.path('requestBody')); + this.addParameter(bodyParameter); } } // add response delegate parameters - for (const responseCode in this.operation.responses) { - const response = this.operation.responses[responseCode]; + const responseMatrix = map(this.operation.responses, (responseCode, response) => { const rState = this.state.path('responses', responseCode); - const c = response.content["application/json"]; - if (c) { - this.addParameter(new CallbackParameter(this, `on${responseCode}`, c.schema, this.state)); - } else { - this.addParameter(new CallbackParameter(this, `on${responseCode}`, undefined, this.state)); + const content = response.content; + + let mimeTypes = Object.getOwnPropertyNames(content); + + // if content node is empty, then the callback returns no data. + // but the different codes may represent different possible outcomes. + if (mimeTypes.length === 0) { + const name = camelCase(fixLeadingNumber(deconstruct(`on ${responseCode}`))); + const callback = this.addParameter(new CallbackParameter(this, name, undefined, this.state)); + return { + responseCode, + responses: [{ + parameter: callback, + deserializor: `await ${name}( new ${ClientRuntime.fullName}.Response { requestMessage = _request,responseMessage = _response }); `, + mimeType: [] + } + + ] + } } - } + + return { + responseCode, responses: map(content, (mimeType, mediaType) => { + const name = (mimeTypes.length === 1 || mimeType === 'application/json') ? + camelCase(fixLeadingNumber(deconstruct(`on ${responseCode}`))) : // the common type (or the only one.) + camelCase(fixLeadingNumber(deconstruct(`on ${responseCode} ${mimeType}`))); + + let callback = mediaType.schema ? + this.addParameter(new CallbackParameter(this, name, mediaType.schema, this.state)) : + this.addParameter(new CallbackParameter(this, name, undefined, this.state)); + + let deserializor: string; + + const ss = mediaType.schema === undefined ? undefinedType : state.project.modelsNamespace.resolveTypeDeclaration(mediaType.schema, true, state); + const responseType = new mscorlib.LibraryType(`${ClientRuntime.fullName}.Response<${ss.use},${undefinedType.use}>`) + + switch (mimeType) { + // figure out the deserializor to use for this content type. + case 'application/json': + case 'text/json': + deserializor = // function* () { + // yield "/* deserialize json */"; + `await ${name}( new ${responseType.use}( async () => ${state.project.supportNamespace.fullName}.JsonSerialization.DeserializeToXXX( Carbon.Json.JsonNode.Parse( await _response.Content.ReadAsStringAsync() ) ) ) { requestMessage = _request,responseMessage = _response }); ` + // }; + break; + case 'application/xml': + deserializor = "/* deserialize xml */"; + + break; + default: + // hmm. not a supported serialization type. + // do we handle this like a stream response? + deserializor = "/*todo: other response type */" + } + + return { + parameter: callback, + mimeType: mediaType.accepts, + deserializor: deserializor + }; + }) + }; + }); + // add cancellationToken parameter const cancellationToken = this.addParameter(new Parameter("cancellationToken", mscorlib.CancellationToken)); // add eventhandler parameter - const listener = this.addParameter(new Parameter("listener", mscorlib.EventListener)); + const listener = this.addParameter(new Parameter("listener", EventListenr)); // add method implementation... this.add(function* () { @@ -68,14 +129,21 @@ export class OperationMethod extends Method { for (const parameter of methodParameters) { // spit out parameter validation - yield parameter.validate; + yield parameter.validatePresenceStatement; + yield parameter.validationStatement; } - yield eventListener.fire("AfterValidation"); + // spit out body parameter validation too + if (bodyParameter) { + yield bodyParameter.validatePresenceStatement; + yield bodyParameter.validationStatement; + } + yield eventListener.fire("AfterValidation"); + yield EOL; yield new Using(listener.value, function* () { - yield new Using(`var _request = new System.Net.Http.HttpRequestMessage(Microsoft.Rest.Method.${operation.method.capitalize()}, "http://wherever/...")`, function* () { + yield new Using(`var _request = new System.Net.Http.HttpRequestMessage(${ClientRuntime.fullName}.Method.${operation.method.capitalize()}, "http://wherever/...")`, function* () { yield eventListener.fire("RequestCreated", '_request'); yield `System.Net.Http.HttpResponseMessage _response = null;`; @@ -83,14 +151,42 @@ export class OperationMethod extends Method { // try statements yield `_response = await this.Sender.SendAsync(_request, ${cancellationToken.value}, ${listener.value});`; yield eventListener.fire("ResponseCreated", '_response'); + yield `var _contentType = (_response.Headers.TryGetValues("Content-Type", out var values) ? System.Linq.Enumerable.FirstOrDefault(values) : string.Empty).ToLowerInvariant();` // add response handlers yield new Switch({ value: `_response.StatusCode` }, function* () { - for (const responseCode in operation.responses) { + for (const eachResponse of responseMatrix) { + if (eachResponse.responseCode !== 'default') { + // each response + yield new Case(`(System.Net.HttpStatusCode)${eachResponse.responseCode}`, function* () { + yield `// on ${eachResponse.responseCode} ... `; + + for (const eachMime of eachResponse.responses) { + if (eachMime.mimeType.length > 0) { + yield `if( _contentType == "${eachMime.mimeType[0]}" )` + yield '{' + yield eachMime.deserializor; + yield '}' + } else { + yield eachMime.deserializor; + } + } + + yield EOL; + }); + } else { + yield new DefaultCase(function* () { + yield "// on default ... "; + yield EOL; + }); + } + } + /* for (const responseCode in operation.responses) { if (responseCode !== 'default') { // each response yield new Case(`(System.Net.HttpStatusCode)${responseCode}`, function* () { yield `// on ${responseCode} ... `; + yield EOL; }); } else { @@ -99,12 +195,12 @@ export class OperationMethod extends Method { yield EOL; }); } - } + }*/ if (!operation.responses["default"]) { // if no default, we need one that handles the rest of the stuff. yield new TerminalDefaultCase(function* () { - yield `throw new Microsoft.Rest.UndeclaredResponseException(_response.StatusCode);`; + yield `throw new ${ClientRuntime.fullName}.UndeclaredResponseException(_response.StatusCode);`; }); } }); @@ -144,6 +240,6 @@ export class FireEvent implements Statement { get implementation(): string { const additionalParameters = this.additionalParameters.length > 0 ? `, ${this.additionalParameters.joinWith(each => typeof each === 'string' ? each : each.value)}` : ``; - return `await ${this.expression.value}.Signal(Microsoft.Rest.Events.${this.eventName}${additionalParameters});`; + return `await ${this.expression.value}.Signal(${ClientRuntime.fullName}.Events.${this.eventName}${additionalParameters});`; } } diff --git a/csharp/lowlevel-generator/operation/parameter.ts b/csharp/lowlevel-generator/operation/parameter.ts index eb37ef6869..870f4afb01 100644 --- a/csharp/lowlevel-generator/operation/parameter.ts +++ b/csharp/lowlevel-generator/operation/parameter.ts @@ -5,37 +5,59 @@ import { Parameter } from "#csharp/code-dom/parameter"; import { Model } from "#remodeler/code-model"; import { State } from "../generator"; -import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; +import { TypeDeclaration, LibraryType } from "../type-declaration"; import { Method } from "#csharp/code-dom/method"; +import { ClientRuntime } from "#csharp/lowlevel-generator/clientruntime"; +import { OneOrMoreStatements } from "#csharp/code-dom/statements/statement"; export class OperationParameter extends Parameter { constructor(parent: Method, param: codemodel.Parameter, state: State, objectInitializer?: Partial) { - super(param.details.name, state.project.modelsNamespace.resolveTypeDeclaration(param.schema, state.path('schema'))); + + super(param.details.name, state.project.modelsNamespace.resolveTypeDeclaration(param.schema, param.required, state.path('schema'))); this.apply(objectInitializer); this.description = param.details.description || ""; - } - - public get validate(): string { - return ``; + public get validatePresenceStatement(): OneOrMoreStatements { + return (this.type).validatePresence(this.name); + } + public get validationStatement(): OneOrMoreStatements { + return (this.type).validateValue(this.name); + } + public get jsonSerializationStatement(): OneOrMoreStatements { + return (this.type).jsonserialize(this.name); + } + public get jsonDeserializationStatement(): OneOrMoreStatements { + return (this.type).jsondeserialize(this.name); } } export class OperationBodyParameter extends Parameter { - constructor(parent: Method, name: string, description: string, schema: codemodel.Schema, state: State, objectInitializer?: Partial) { - super(name, state.project.modelsNamespace.resolveTypeDeclaration(schema, state.path('schema'))); + constructor(parent: Method, name: string, description: string, schema: codemodel.Schema, required: boolean, state: State, objectInitializer?: Partial) { + super(name, state.project.modelsNamespace.resolveTypeDeclaration(schema, required, state.path('schema'))); this.apply(objectInitializer); this.description = description || schema.details.description || ""; - } + public get validatePresenceStatement(): OneOrMoreStatements { + return (this.type).validatePresence(this.name); + } + public get validationStatement(): OneOrMoreStatements { + return (this.type).validateValue(this.name); + } + public get jsonSerializationStatement(): OneOrMoreStatements { + return (this.type).jsonserialize(this.name); + } + public get jsonDeserializationStatement(): OneOrMoreStatements { + return (this.type).jsondeserialize(this.name); + } + } -export const undefinedType: TypeDeclaration = new mscorlib.LibraryType("Microsoft.Rest.undefined"); +export const undefinedType: TypeDeclaration = new LibraryType(`${ClientRuntime.fullName}.undefined`); export class CallbackParameter extends Parameter { constructor(parent: Method, name: string, bodyType: codemodel.Schema | undefined /*, headerType: codemodel.Schema*/, state: State, objectInitializer?: Partial) { - const ss = bodyType === undefined ? undefinedType : state.project.modelsNamespace.resolveTypeDeclaration(bodyType, state); - super(name, new mscorlib.LibraryType(`Microsoft.Rest.OnResponse<${ss.use},${undefinedType.use}>`)); + const ss = bodyType === undefined ? undefinedType : state.project.modelsNamespace.resolveTypeDeclaration(bodyType, true, state); + super(name, new mscorlib.LibraryType(`${ClientRuntime.fullName}.OnResponse<${ss.use},${undefinedType.use}>`)); this.apply(objectInitializer); } } \ No newline at end of file diff --git a/csharp/lowlevel-generator/primitives/Uuid.ts b/csharp/lowlevel-generator/primitives/Uuid.ts index 8d6502d8a0..a5507fcf3d 100644 --- a/csharp/lowlevel-generator/primitives/Uuid.ts +++ b/csharp/lowlevel-generator/primitives/Uuid.ts @@ -1,4 +1,4 @@ -import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; +import { TypeDeclaration } from "../type-declaration"; export class Uuid implements TypeDeclaration { get implementation(): string { @@ -7,7 +7,16 @@ export class Uuid implements TypeDeclaration { get use(): string { return `Guid` } - validation(propertyName: string): string { - throw new Error("Method not implemented."); + public validatePresence(propertyName: string): string { + return ``; + } + validateValue(propertyName: string): string { + return `/* uuid validate value for ${propertyName} */`; + } + jsonserialize(propertyName: string): string { + return `/* uuid json serialize for ${propertyName} */`; + } + jsondeserialize(propertyName: string): string { + return `/* uuid json deserialize for ${propertyName} */`; } } \ No newline at end of file diff --git a/csharp/lowlevel-generator/primitives/array.ts b/csharp/lowlevel-generator/primitives/array.ts index ea80d3279a..6d3224476d 100644 --- a/csharp/lowlevel-generator/primitives/array.ts +++ b/csharp/lowlevel-generator/primitives/array.ts @@ -1,4 +1,4 @@ -import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; +import { TypeDeclaration } from "../type-declaration"; export class ArrayOf implements TypeDeclaration { @@ -11,7 +11,16 @@ export class ArrayOf implements TypeDeclaration { get use(): string { return `${this.type.use}[]`; } - validation(propertyName: string): string { - throw new Error("Method not implemented."); + public validatePresence(propertyName: string): string { + return ``; + } + validateValue(propertyName: string): string { + return `/* validate array values for ${propertyName} */`; + } + jsonserialize(propertyName: string): string { + return `/* json serialize for ${propertyName} */`; + } + jsondeserialize(propertyName: string): string { + return `/* json deserialize for ${propertyName} */`; } } \ No newline at end of file diff --git a/csharp/lowlevel-generator/primitives/boolean.ts b/csharp/lowlevel-generator/primitives/boolean.ts index 39926718c2..6368ffb04f 100644 --- a/csharp/lowlevel-generator/primitives/boolean.ts +++ b/csharp/lowlevel-generator/primitives/boolean.ts @@ -1,4 +1,4 @@ -import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; +import { TypeDeclaration } from "../type-declaration"; export class Boolean implements TypeDeclaration { get implementation(): string { @@ -7,7 +7,26 @@ export class Boolean implements TypeDeclaration { get use(): string { return `bool` } - validation(propertyName: string): string { - throw new Error("Method not implemented."); + public validatePresence(propertyName: string): string { + return ``; + } + validateValue(propertyName: string): string { + return `/* boolean validate value for ${propertyName} */`; + } + jsonserialize(propertyName: string): string { + return `/* boolean json serialize for ${propertyName} */`; + } + jsondeserialize(propertyName: string): string { + return `/* boolean json deserialize for ${propertyName} */`; + } +} + + +export class NullableBoolean extends Boolean { + get implementation(): string { + return `bool?`; + }; + get use(): string { + return `bool?`; } } \ No newline at end of file diff --git a/csharp/lowlevel-generator/primitives/byte-array.ts b/csharp/lowlevel-generator/primitives/byte-array.ts index 0c7764f69a..20a7b05290 100644 --- a/csharp/lowlevel-generator/primitives/byte-array.ts +++ b/csharp/lowlevel-generator/primitives/byte-array.ts @@ -1,4 +1,4 @@ -import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; +import { TypeDeclaration } from "../type-declaration"; export class ByteArray implements TypeDeclaration { get implementation(): string { @@ -7,7 +7,16 @@ export class ByteArray implements TypeDeclaration { get use(): string { return `byte[]`; } - validation(propertyName: string): string { - throw new Error("Method not implemented."); + public validatePresence(propertyName: string): string { + return ``; + } + validateValue(propertyName: string): string { + return `/* byte array validate value for ${propertyName} */`; + } + jsonserialize(propertyName: string): string { + return `/* byte array json serialize for ${propertyName} */`; + } + jsondeserialize(propertyName: string): string { + return `/* byte array json deserialize for ${propertyName} */`; } } \ No newline at end of file diff --git a/csharp/lowlevel-generator/primitives/char.ts b/csharp/lowlevel-generator/primitives/char.ts index 7d980bd3fe..5e05399756 100644 --- a/csharp/lowlevel-generator/primitives/char.ts +++ b/csharp/lowlevel-generator/primitives/char.ts @@ -1,4 +1,4 @@ -import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; +import { TypeDeclaration } from "../type-declaration"; export class Char implements TypeDeclaration { constructor(private choices?: Array) { @@ -11,12 +11,15 @@ export class Char implements TypeDeclaration { get use(): string { return 'char'; } + public validatePresence(propertyName: string): string { + return ``; + } valueRequired(propertyName: string): string { return `` }; - validation(propertyName: string): string { + validateValue(propertyName: string): string { return ` ${this.validateEnum(propertyName)} `.trim(); @@ -29,4 +32,12 @@ ${this.validateEnum(propertyName)} } return '// todo validate enum choices'; } + + jsonserialize(propertyName: string): string { + return `/* char json serialize for ${propertyName} */`; + } + jsondeserialize(propertyName: string): string { + return `/* char json deserialize for ${propertyName} */`; + } + } diff --git a/csharp/lowlevel-generator/primitives/date-time.ts b/csharp/lowlevel-generator/primitives/date-time.ts index af62a0c81a..d97eeff414 100644 --- a/csharp/lowlevel-generator/primitives/date-time.ts +++ b/csharp/lowlevel-generator/primitives/date-time.ts @@ -1,4 +1,4 @@ -import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; +import { TypeDeclaration } from "../type-declaration"; export class DateTime implements TypeDeclaration { get implementation(): string { @@ -7,8 +7,17 @@ export class DateTime implements TypeDeclaration { get use(): string { return `System.DateTime` } - validation(propertyName: string): string { - throw new Error("Method not implemented."); + public validatePresence(propertyName: string): string { + return ``; + } + validateValue(propertyName: string): string { + return `/* datetime validate value for ${propertyName} */`; + } + jsonserialize(propertyName: string): string { + return `/* datetime json serialize for ${propertyName} */`; + } + jsondeserialize(propertyName: string): string { + return `/* datetime json deserialize for ${propertyName} */`; } } @@ -19,8 +28,17 @@ export class DateTime1123 implements TypeDeclaration { get use(): string { return `System.DateTime` } - validation(propertyName: string): string { - throw new Error("Method not implemented."); + public validatePresence(propertyName: string): string { + return ``; + } + validateValue(propertyName: string): string { + return `/* datetime1123 validate value for ${propertyName} */`; + } + jsonserialize(propertyName: string): string { + return `/* datetime1123 json serialize for ${propertyName} */`; + } + jsondeserialize(propertyName: string): string { + return `/* datetime1123 json deserialize for ${propertyName} */`; } } diff --git a/csharp/lowlevel-generator/primitives/date.ts b/csharp/lowlevel-generator/primitives/date.ts index 9b9e427853..df86764fdf 100644 --- a/csharp/lowlevel-generator/primitives/date.ts +++ b/csharp/lowlevel-generator/primitives/date.ts @@ -1,4 +1,4 @@ -import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; +import { TypeDeclaration } from "../type-declaration"; export class Date implements TypeDeclaration { get implementation(): string { @@ -7,7 +7,16 @@ export class Date implements TypeDeclaration { get use(): string { return `System.DateTime` } - validation(propertyName: string): string { - throw new Error("Method not implemented."); + public validatePresence(propertyName: string): string { + return ``; + } + validateValue(propertyName: string): string { + return `/* date validate value for ${propertyName} */`; + } + jsonserialize(propertyName: string): string { + return `/* date json serialize for ${propertyName} */`; + } + jsondeserialize(propertyName: string): string { + return `/* date json deserialize for ${propertyName} */`; } } \ No newline at end of file diff --git a/csharp/lowlevel-generator/primitives/duration.ts b/csharp/lowlevel-generator/primitives/duration.ts index a96988f0e2..6bb37d35bd 100644 --- a/csharp/lowlevel-generator/primitives/duration.ts +++ b/csharp/lowlevel-generator/primitives/duration.ts @@ -1,4 +1,4 @@ -import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; +import { TypeDeclaration } from "../type-declaration"; export class Duration implements TypeDeclaration { get implementation(): string { @@ -7,7 +7,16 @@ export class Duration implements TypeDeclaration { get use(): string { return `TimeSpan` } - validation(propertyName: string): string { - throw new Error("Method not implemented."); + public validatePresence(propertyName: string): string { + return ``; + } + validateValue(propertyName: string): string { + return `/* duration validate value for ${propertyName} */`; + } + jsonserialize(propertyName: string): string { + return `/* duration json serialize for ${propertyName} */`; + } + jsondeserialize(propertyName: string): string { + return `/* duration json deserialize for ${propertyName} */`; } } \ No newline at end of file diff --git a/csharp/lowlevel-generator/primitives/floatingpoint.ts b/csharp/lowlevel-generator/primitives/floatingpoint.ts index 43fc473b09..c56edd7982 100644 --- a/csharp/lowlevel-generator/primitives/floatingpoint.ts +++ b/csharp/lowlevel-generator/primitives/floatingpoint.ts @@ -1,4 +1,4 @@ -import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; +import { TypeDeclaration } from "../type-declaration"; export class Float implements TypeDeclaration { get implementation(): string { @@ -7,7 +7,16 @@ export class Float implements TypeDeclaration { get use(): string { return `float` } - validation(propertyName: string): string { - throw new Error("Method not implemented."); + public validatePresence(propertyName: string): string { + return ``; + } + validateValue(propertyName: string): string { + return `/* float validate value for ${propertyName} */`; + } + jsonserialize(propertyName: string): string { + return `/* float json serialize for ${propertyName} */`; + } + jsondeserialize(propertyName: string): string { + return `/* float json deserialize for ${propertyName} */`; } } \ No newline at end of file diff --git a/csharp/lowlevel-generator/primitives/integer.ts b/csharp/lowlevel-generator/primitives/integer.ts index 6255530b71..3838ba5331 100644 --- a/csharp/lowlevel-generator/primitives/integer.ts +++ b/csharp/lowlevel-generator/primitives/integer.ts @@ -1,4 +1,4 @@ -import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; +import { TypeDeclaration } from "../type-declaration"; export class Integer implements TypeDeclaration { get implementation(): string { @@ -7,7 +7,16 @@ export class Integer implements TypeDeclaration { get use(): string { return `int`; } - validation(propertyName: string): string { - throw new Error("Method not implemented."); + public validatePresence(propertyName: string): string { + return ``; + } + validateValue(propertyName: string): string { + return `/* integer validate value for ${propertyName} */`; + } + jsonserialize(propertyName: string): string { + return `/* integer json serialize for ${propertyName} */`; + } + jsondeserialize(propertyName: string): string { + return `/* integer json deserialize for ${propertyName} */`; } } \ No newline at end of file diff --git a/csharp/lowlevel-generator/primitives/stream.ts b/csharp/lowlevel-generator/primitives/stream.ts index e9d01931ba..d4d712d2ab 100644 --- a/csharp/lowlevel-generator/primitives/stream.ts +++ b/csharp/lowlevel-generator/primitives/stream.ts @@ -1,4 +1,4 @@ -import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; +import { TypeDeclaration } from "../type-declaration"; export class Stream implements TypeDeclaration { get implementation(): string { @@ -7,7 +7,16 @@ export class Stream implements TypeDeclaration { get use(): string { return `System.IO.Stream` } - validation(propertyName: string): string { - throw new Error("Method not implemented."); + public validatePresence(propertyName: string): string { + return ``; + } + validateValue(propertyName: string): string { + return `/* stream validate value for ${propertyName} */`; + } + jsonserialize(propertyName: string): string { + return `/* stream json serialize for ${propertyName} */`; + } + jsondeserialize(propertyName: string): string { + return `/* stream json deserialize for ${propertyName} */`; } } \ No newline at end of file diff --git a/csharp/lowlevel-generator/primitives/string.ts b/csharp/lowlevel-generator/primitives/string.ts index da9150427c..13945cd3a3 100644 --- a/csharp/lowlevel-generator/primitives/string.ts +++ b/csharp/lowlevel-generator/primitives/string.ts @@ -1,4 +1,4 @@ -import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; +import { TypeDeclaration } from "../type-declaration"; export class String implements TypeDeclaration { constructor(private minLength?: number, private maxLength?: number, private pattern?: string, private choices?: Array) { @@ -13,12 +13,13 @@ export class String implements TypeDeclaration { valueRequired(propertyName: string): string { return ` -if( $VALUE == null ) { +if( $VALUE == null ) +{ $ERROR; }`.trim(); }; - validation(propertyName: string): string { + validateValue(propertyName: string): string { return ` ${this.validateMinLength(propertyName)} ${this.validateMaxLength(propertyName)} @@ -28,37 +29,46 @@ ${this.validateEnum(propertyName)} ; } + + public validatePresence(propertyName: string): string { + return `await listener.AssertNotNull(nameof(${propertyName}),${propertyName});`.trim(); + } + private validateMinLength(propertyName: string): string { if (!this.minLength) { return ''; } - return ` -if( ${propertyName}.Length < ${this.minLength}) { - throw new Exception("${propertyName} length is less than ${this.minLength}"); -}`.trim(); + return `await listener.AssertMinimumLength(nameof(${propertyName}),${propertyName},${this.minLength});` } private validateMaxLength(propertyName: string): string { if (!this.minLength) { return ''; } - return ` -if( ${propertyName}.Length > ${this.maxLength}) { - throw new Exception("${propertyName} length is greatere than ${this.maxLength}"); -}`.trim(); + return `await listener.AssertMaximumLength(nameof(${propertyName}),${propertyName},${this.minLength});` } private validateRegex(propertyName: string): string { if (!this.pattern) { return ''; } - return ` -if( !System.Text.RegularExpressions.Regex.Match(${propertyName}, "${this.pattern}") ) {} - throw new Exception("${propertyName} does not validate against patter '${this.pattern}'"); -}`.trim(); + return `await listener.AssertRegEx(nameof(${propertyName}),${propertyName},@"${this.pattern}");` } private validateEnum(propertyName: string): string { if (!this.choices) { return ''; } - return '// todo validate enum choices'; + return `await listener.AssertEnum(nameof(${propertyName}),${propertyName},${this.choices.joinWith((v) => `@"${v}"`)});` + } + jsonserialize(propertyName: string): string { + return `/* string json serialize for ${propertyName} */`; + } + jsondeserialize(propertyName: string): string { + return `/* string json deserialize for ${propertyName} */`; } } + + +export class NullableString extends String { + public validatePresence(propertyName: string): string { + return ``; + } +} \ No newline at end of file diff --git a/csharp/lowlevel-generator/primitives/wildcard.ts b/csharp/lowlevel-generator/primitives/wildcard.ts index 61ba179a4d..548a1bb896 100644 --- a/csharp/lowlevel-generator/primitives/wildcard.ts +++ b/csharp/lowlevel-generator/primitives/wildcard.ts @@ -1,4 +1,4 @@ -import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; +import { TypeDeclaration } from "../type-declaration"; export class Wildcard implements TypeDeclaration { @@ -11,8 +11,17 @@ export class Wildcard implements TypeDeclaration { get use(): string { return `System.Collections.Generic.Dictionary`; } - validation(propertyName: string): string { - throw new Error("Method not implemented."); + public validatePresence(propertyName: string): string { + return ``; + } + validateValue(propertyName: string): string { + return `/* wildcard validate value for ${propertyName} */`; + } + jsonserialize(propertyName: string): string { + return `/* wildcard json serialize for ${propertyName} */`; + } + jsondeserialize(propertyName: string): string { + return `/* wildcard json deserialize for ${propertyName} */`; } } @@ -29,7 +38,16 @@ export class UntypedWildcard implements TypeDeclaration { get use(): string { return `System.Collections.Generic.Dictionary`; } - validation(propertyName: string): string { - throw new Error("Method not implemented."); + public validatePresence(propertyName: string): string { + return ``; + } + validateValue(propertyName: string): string { + return `/* untyped wildcard validate value for ${propertyName} */`; + } + jsonserialize(propertyName: string): string { + return `/* untyped wildcard boolean json serialize for ${propertyName} */`; + } + jsondeserialize(propertyName: string): string { + return `/* untyped wildcard json deserialize for ${propertyName} */`; } } diff --git a/csharp/lowlevel-generator/private-data.ts b/csharp/lowlevel-generator/private-data.ts new file mode 100644 index 0000000000..1fd5ae82e3 --- /dev/null +++ b/csharp/lowlevel-generator/private-data.ts @@ -0,0 +1,10 @@ +import { TypeDeclaration } from "#csharp/lowlevel-generator/type-declaration"; +import { ModelClass } from "#csharp/lowlevel-generator/model/class"; +import { ModelInterface } from "#csharp/lowlevel-generator/model/interface"; +import { Dictionary } from "#remodeler/common"; + +export interface PrivateData extends Dictionary { + typeDeclaration?: TypeDeclaration; + classImplementation?: ModelClass; + interfaceImplementation?: ModelInterface; +} \ No newline at end of file diff --git a/csharp/lowlevel-generator/project.ts b/csharp/lowlevel-generator/project.ts index 4d8298fa23..6f8d6bb470 100644 --- a/csharp/lowlevel-generator/project.ts +++ b/csharp/lowlevel-generator/project.ts @@ -8,6 +8,8 @@ import { ModelClass } from "./model/class"; import { ModelsNamespace } from "./model/namespace"; import { ServiceNamespace } from "./operation/namespace"; import { SupportNamespace } from "./support/namespace"; +import { JsonSerializerClass } from "#csharp/lowlevel-generator/support/json-serializer"; +import { Import } from "#csharp/code-dom/import"; export class Project extends codeDomProject { @@ -24,7 +26,13 @@ export class Project extends codeDomProject { this.addNamespace(this.modelsNamespace = new ModelsNamespace(this.serviceNamespace, state.model.components.schemas, state.path('components', 'schemas'))); // create API class - new ApiClass(this, state); + new ApiClass(this.serviceNamespace, state); + + // create serialization support + new JsonSerializerClass(this.supportNamespace, state) + this.modelsNamespace.addUsing(new Import(this.supportNamespace.fullName)); + + // abort now if we have any errors. state.checkpoint(); diff --git a/csharp/lowlevel-generator/support/enum.ts b/csharp/lowlevel-generator/support/enum.ts index 9bb7b74c6f..ee092b5419 100644 --- a/csharp/lowlevel-generator/support/enum.ts +++ b/csharp/lowlevel-generator/support/enum.ts @@ -15,8 +15,9 @@ import { Method } from "#csharp/code-dom/method"; import * as mscorlib from "#csharp/code-dom/mscorlib"; import { Struct } from "#csharp/code-dom/struct"; import { Operator } from "#csharp/code-dom/operator"; +import { TypeDeclaration } from "#csharp/lowlevel-generator/type-declaration"; -export class EnumClass extends Struct { +export class EnumClass extends Struct implements TypeDeclaration { constructor(schema: Schema, state: State, objectInitializer?: Partial) { if (!schema.details.enum) { throw new Error("ENUM AINT XMSENUM"); @@ -100,7 +101,18 @@ export class EnumClass extends Struct { isOverride: true, description: `Returns hashCode for enum ${this.name}`, })).add(`return this.${backingField.value}.GetHashCode();`); + } - + public validateValue(propertyName: string): string { + return ``; + } + public validatePresence(propertyName: string): string { + return ``; + } + jsonserialize(propertyName: string): string { + return '/***/'; + } + jsondeserialize(propertyName: string): string { + return '/***/'; } } \ No newline at end of file diff --git a/csharp/lowlevel-generator/support/json-serializer.ts b/csharp/lowlevel-generator/support/json-serializer.ts new file mode 100644 index 0000000000..9d8a9e9f59 --- /dev/null +++ b/csharp/lowlevel-generator/support/json-serializer.ts @@ -0,0 +1,101 @@ +import { Class } from "#csharp/code-dom/class"; +import { Namespace } from "#csharp/code-dom/namespace"; +import { State } from "#csharp/lowlevel-generator/generator"; +import { Method, PartialMethod } from "#csharp/code-dom/method"; +import { JsonNode, JsonObject } from "#csharp/lowlevel-generator/clientruntime"; +import { Parameter } from "#csharp/code-dom/parameter"; +import * as mscorlib from "#csharp/code-dom/mscorlib"; +import { Interface } from "#csharp/code-dom/interface"; +import { AccessModifier } from "#csharp/code-dom/access-modifier"; +import { ParameterModifier } from "#csharp/code-dom/parameter-modifier"; +import { EOL } from "#common/text-manipulation"; +import { ModelClass } from "#csharp/lowlevel-generator/model/class"; +import { PrivateData } from "#csharp/lowlevel-generator/private-data"; + +export class JsonSerializerClass extends Class { + + constructor(namespace: Namespace, protected state: State, objectInitializer?: Partial) { + super(namespace, "JsonSerialization"); + this.apply(objectInitializer); + + this.partial = true; + this.isStatic = true; + + const tojson = this.addMethod(new Method("ToJson", JsonNode, { isStatic: true })); + const objP = tojson.addParameter(new Parameter("obj", mscorlib.ThisObject)); + const container = tojson.addParameter(new Parameter("container", JsonObject, { defaultInitializer: `= null` })); + tojson.add(`return null;`); + + // add the json serialize method to each model class + for (const each in state.model.components.schemas) { + const schema = state.model.components.schemas[each]; + + if (schema.details.privateData.classImplementation) { + const clss: ModelClass = schema.details.privateData.classImplementation; + const iface: Interface = schema.details.privateData.interfaceImplementation; + + // make sure the model class is partial. + clss.partial = true; + + // add partial methods for future customization + const btj = clss.addMethod(new PartialMethod("BeforeToJson", mscorlib.Void, { + accessModifier: AccessModifier.Default, + parameters: [ + new Parameter("container", JsonObject, { modifier: ParameterModifier.Ref, description: "The JSON container that the serialization result will be placed in." }), + new Parameter("returnNow", mscorlib.Bool, { modifier: ParameterModifier.Ref, description: "Determines if the rest of the serialization should be processed, or if the method should return instantly." }), + ] + })); + + const atj = clss.addMethod(new PartialMethod("AfterToJson", mscorlib.Void, { + accessModifier: AccessModifier.Default, + parameters: [ + new Parameter("container", JsonObject, { modifier: ParameterModifier.Ref, description: "The JSON container that the serialization result will be placed in." }), + ] + })) + + const toJsonMethod = clss.addMethod(new Method(tojson.name, JsonNode, )); + const container = toJsonMethod.addParameter(new Parameter("container", JsonObject)); + toJsonMethod.add(function* () { + yield `var result = container ?? new ${JsonObject.use}();` + yield EOL; + + yield `bool returnNow = false;`; + yield `${btj.name}(ref result, ref returnNow);`; + yield ` +if( returnNow ) +{ + return result; +}`.trim(); + + // get serialization statements + yield clss.serializeStatements; + + + yield `${atj.name}(ref result);`; + yield `return result;`; + }); + + } + } + // create internal method to find deserializer method + // ie GetJsonDeserializerFor_INTERFACENAME(JsonNode json) + + // create method to deserialize JsonNode to the target type. + // ie Deserialize_INTERFACENAME(JsonNode json) + + // add master 'toJson' method + + // add internal method to schema class for bool CanDeserialize(JsonNode json) + + // add internal method to schema class for static IFOO FromJson(JsonNode json) + // remember two partial methods: + // static partial void BeforeFromJson(JsonObject json, ref IOtherResource result); + // static partial void AfterFromJson(JsonObject json, ref IOtherResource result); + + // add internal method to schema class for ToJson(JsonObject container = null) + // remember two partial methods: + // partial void BeforeToJson(ref JsonObject container, ref bool returnNow); + // partial void AfterToJson(ref JsonObject container); + + } +} diff --git a/csharp/lowlevel-generator/type-declaration.ts b/csharp/lowlevel-generator/type-declaration.ts new file mode 100644 index 0000000000..e717b15387 --- /dev/null +++ b/csharp/lowlevel-generator/type-declaration.ts @@ -0,0 +1,36 @@ +import { TypeDeclaration as BaseTypeDeclaration } from "#csharp/code-dom/type-declaration"; +import { OneOrMoreStatements } from "#csharp/code-dom/statements/statement"; +export interface TypeDeclaration extends BaseTypeDeclaration { + validatePresence(propertyName: string): OneOrMoreStatements; + validateValue(propertyName: string): OneOrMoreStatements; + jsonserialize(propertyName: string): OneOrMoreStatements; + jsondeserialize(propertyName: string): OneOrMoreStatements; +} + +export class LibraryType implements TypeDeclaration { + constructor(private fullName: string) { + } + + public get use(): string { + return `${this.fullName}`; + } + + public validatePresence(propertyName: string): OneOrMoreStatements { + return ``; + } + + public validateValue(propertyName: string): OneOrMoreStatements { + return ''; + } + + public get implementation(): string { + return ``; + } + + jsonserialize(propertyName: string): OneOrMoreStatements { + return ``; + } + jsondeserialize(propertyName: string): OneOrMoreStatements { + return ``; + } +} \ No newline at end of file diff --git a/main.ts b/main.ts index 994a2568c0..c17a990bd6 100644 --- a/main.ts +++ b/main.ts @@ -5,9 +5,11 @@ import { AutoRestExtension, } from "@microsoft.azure/autorest-extension-base"; import { process as remodeler } from "./remodeler/main"; +import { process as inferrer } from "./remodeler/inferrer"; + import { process as llcsharp } from "./csharp/lowlevel-generator/main"; import { process as csnamer } from "./csharp/namer"; -import { process as inferrer } from "./csharp/inferrer"; +import { process as csinferrer } from "./csharp/inferrer"; import { CommaChar } from "#common/text-manipulation"; @@ -18,9 +20,10 @@ async function main() { // remodeler extensions pluginHost.Add("remodeler", remodeler); + pluginHost.Add("inferrer", inferrer); // csharp extensions - pluginHost.Add("csinferrer", inferrer); + pluginHost.Add("csinferrer", csinferrer); pluginHost.Add("csnamer", csnamer); pluginHost.Add("llcsharp", llcsharp); diff --git a/package.json b/package.json index 0aa34a61ff..2d7a3f3b95 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "semver": "^5.4.1", "shx": "^0.2.2", "tspath": "^1.2.10", - "typescript": "2.7.2" + "typescript": "insiders" }, "dependencies": { "@microsoft.azure/tasks": "^2.0.12", diff --git a/remodeler/code-model.ts b/remodeler/code-model.ts index b9bd00a03a..5106fd6c17 100644 --- a/remodeler/code-model.ts +++ b/remodeler/code-model.ts @@ -32,11 +32,8 @@ export class Initializer { constructor(initializer?: Partial) { if (initializer) { for (const i in (initializer)) { - if ((initializer)[i]) { - (this)[i] = (initializer)[i] - }; + (this)[i] = (initializer)[i] } - // Object.assign(this, initializer); } } } @@ -164,6 +161,8 @@ export interface ClientDetails extends Details { } export interface PropertyDetails extends Details { + required: boolean; + } export interface ParameterDetails extends Details { @@ -210,6 +209,7 @@ export class PropertyReference extends Initializer> impl super(initializer); this.details = (this).details || {}; this.details.name = this.details.name || name; + this.details.required = this.details.required || false; this.details.privateData = {}; } } @@ -307,10 +307,15 @@ export class Link extends Initializer implements Link { } } +export interface MediaType { + accepts: Array; +} + export class MediaType extends Initializer implements MediaType { constructor(initializer?: Partial) { super(initializer); this.encoding = (this).encoding || new Dictionary(); + this.accepts = (this).accepts || new Array(); } } @@ -352,6 +357,7 @@ export class HttpOperation extends Initializer implements HttpOpe this.callbacks = (this).callbacks || new Dictionary>(); this.security = (this).security || new Array(); this.servers = (this).servers || new Array(); + this.deprecated = (this).deprecated || false; } } @@ -365,7 +371,9 @@ export class Parameter extends Initializer implements Parameter, Impl this.details.name = this.details.name || name; this.details.location = implementation; this.details.privateData = {}; - + this.deprecated = (this).deprecated || false; + this.required = (this).required || false; + this.allowEmptyValue = (this).allowEmptyValue || false; if (inWhere === ParameterLocation.Path) { this.required = true; } @@ -568,10 +576,10 @@ export interface ExternalDocumentation extends Extensions { export interface Header extends Extensions, Partial, Partial, Partial, Partial { description?: string; - required?: boolean; - deprecated?: boolean; - allowEmptyValue?: boolean; - allowReserved?: boolean; + required: Optional; + deprecated: Optional; + allowEmptyValue: Optional; + allowReserved: Optional; } export interface ImplicitOAuthFlow extends Extensions { @@ -637,7 +645,7 @@ export interface HttpOperation extends Extensions, Implementation; responses: Dictionary>; callbacks: Optional>>; - deprecated?: boolean; + deprecated: Optional; security: Optional>; servers: Optional>; } @@ -677,9 +685,9 @@ export interface Parameter extends Partial, Partial, Part in: ParameterLocation; description?: string; - allowEmptyValue?: boolean; - deprecated?: boolean; - required?: boolean; + allowEmptyValue: Optional; + deprecated: Optional; + required: Optional; style?: EncodingStyle; allowReserved?: boolean; diff --git a/remodeler/inferrer.ts b/remodeler/inferrer.ts new file mode 100644 index 0000000000..df6e229be6 --- /dev/null +++ b/remodeler/inferrer.ts @@ -0,0 +1,28 @@ +import { Host, ArtifactMessage, Channel } from "@microsoft.azure/autorest-extension-base"; +import { deserialize, serialize } from "#common/yaml"; +import { processCodeModel } from "#common/process-code-model"; +import { ModelState } from "#common/model-state"; +import { Model } from "remodeler/code-model"; +import { map } from "#common/text-manipulation"; + +export async function process(service: Host) { + return await processCodeModel(inferStuff, service); +} + +async function inferStuff(model: Model, service: Host): Promise { + map(model.components.operations, (key, operation) => { + // TODO: mimetypes might start with the mimetype and have extra stuff after + // this should be fixed if it happens. + + // if an operation response has a application/json and text/json, remove the text/json and add that mime type to the list of 'accepts' + return map(operation.responses, (key, response) => { + const content = response.content; + if (content["application/json"] && content["text/json"]) { + content["application/json"].accepts.push("text/json"); + delete content["text/json"]; + } + return null; + }); + }); + return model; +} \ No newline at end of file diff --git a/remodeler/remodeler.ts b/remodeler/remodeler.ts index b653440f21..9a22b114e0 100644 --- a/remodeler/remodeler.ts +++ b/remodeler/remodeler.ts @@ -217,6 +217,7 @@ export class Remodeler { description: Interpretations.getDescription(Interpretations.getDescription("", newPropSchema), property), name: Interpretations.getName(propertyName, propertySchema.instance), deprecationMessage: Interpretations.getDeprecationMessage(original), + required: original.required ? original.required.indexOf(propertyName) > -1 : false, privateData: {}, } }) @@ -354,11 +355,12 @@ export class Remodeler { return original ? CopyDictionary(original, (v) => this.copyEncoding(v, original[v])) : new Dictionary(); } - copyMediaType = (schemaName: string, original: OpenAPI.MediaType): MediaType => { + copyMediaType = (mimeType: string, key: string, original: OpenAPI.MediaType): MediaType => { return new MediaType({ - schema: original.schema ? this.refOrAdd(schemaName, this.dereference(original.schema), this.model.components.schemas, this.copySchema) : undefined, + schema: original.schema ? this.refOrAdd(key, this.dereference(original.schema), this.model.components.schemas, this.copySchema) : undefined, encoding: this.copyEncodings(original.encoding), extensions: getExtensionProperties(original), + accepts: [mimeType] }); } @@ -388,7 +390,7 @@ export class Remodeler { if (original.content) { for (const mediaType in original.content) { - response.content[mediaType] = this.copyMediaType(`${name}.${mediaType}`, original.content[mediaType]); + response.content[mediaType] = this.copyMediaType(mediaType, `${name}.${mediaType}`, original.content[mediaType]); } } @@ -404,7 +406,7 @@ export class Remodeler { if (original.content) { for (const mediaType in original.content) { - requestBody.content[mediaType] = this.copyMediaType(`${name}.${mediaType}`, original.content[mediaType]); + requestBody.content[mediaType] = this.copyMediaType(mediaType, `${name}.${mediaType}`, original.content[mediaType]); } }