diff --git a/README.md b/README.md index 626ea167cd..58ec038263 100644 --- a/README.md +++ b/README.md @@ -52,14 +52,14 @@ scope-remodeler/emitter: ``` yaml $(llcsharp) pipeline: - llcsharp/inferrer: + llcsharp/csinferrer: scope: llcsharp input: remodeler output-artifact: code-model-v2 llcsharp/csnamer: scope: llcsharp - input: inferrer + input: csinferrer output-artifact: code-model-v2 llcsharp/generate: diff --git a/inferrer/inferrer.ts b/common/process-code-model.ts similarity index 74% rename from inferrer/inferrer.ts rename to common/process-code-model.ts index 946b00c444..358587899d 100644 --- a/inferrer/inferrer.ts +++ b/common/process-code-model.ts @@ -4,7 +4,7 @@ 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) { +export async function processCodeModel(processExtension: (input: Model, service: Host) => Promise, service: Host) { try { // Get the list of files const files = await service.ListInputs(); @@ -17,9 +17,9 @@ export async function process(service: Host) { const original = await service.ReadFile(files[0]); // deserialize - const codeModel = await deserialize(await service.ReadFile(files[0]), files[0]); - + let codeModel = await deserialize(await service.ReadFile(files[0]), files[0]); + codeModel = await processExtension(codeModel, service); // output the model await service.WriteFile("code-model-v2.yaml", serialize(codeModel), undefined/*,"code-model-v2"*/); diff --git a/common/text-manipulation.ts b/common/text-manipulation.ts index 88e7b36d97..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()); @@ -131,7 +141,7 @@ export function deconstruct(identifier: string): Array { } export function fixLeadingNumber(identifier: Array): Array { - if (identifier.length > 0 && /\d+/.exec(identifier[0])) { + if (identifier.length > 0 && /^\d+/.exec(identifier[0])) { return [...convert(parseInt(identifier[0])), ...identifier.slice(1)]; } return identifier; 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 e2cf614891..ad1bc67402 100644 --- a/csharp/code-dom/class.ts +++ b/csharp/code-dom/class.ts @@ -7,25 +7,31 @@ import { Property } from "./property"; 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; - constructor(parent: Namespace, name: string, public parentClass?: Class, interfaces = new Array(), genericParameters = new Array(), where?: string) { - super(parent, name, interfaces, genericParameters, where); - parent.addClass(this); + constructor(namespace: Namespace, name: string, public parent?: Class, objectIntializer?: Partial) { + super(namespace, name); + this.apply(objectIntializer); + namespace.addClass(this); } public get declaration(): string { - const colon = (this.parentClass || this.interfaces.length > 0) ? ' : ' : ''; - const comma = (this.parentClass && this.interfaces.length > 0) ? ", " : ''; + const colon = (this.parent || this.interfaces.length > 0) ? ' : ' : ''; + const comma = (this.parent && this.interfaces.length > 0) ? ", " : ''; - const extendsClass = this.parentClass ? this.parentClass.fullName : ''; + const extendsClass = this.parent ? this.parent.fullName : ''; 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}class ${this.name}${colon}${extendsClass}${comma}${implementsInterfaces} +${this.accessModifier} ${stat}${partial}${this.classOrStruct} ${this.name}${colon}${extendsClass}${comma}${implementsInterfaces} `.trim(); } @@ -33,7 +39,6 @@ ${this.accessModifier} ${partial}class ${this.name}${colon}${extendsClass}${comm const fields = this.fields.sort(sortByName).map(m => m.declaration).join(EOL); const methods = this.methods.sort(sortByName).map(m => m.implementation).join(EOL); const properties = this.properties.sort(sortByName).map(m => m.declaration).join(EOL); - return ` ${this.declaration} { ${indent(fields, 1)} @@ -53,4 +58,4 @@ ${indent(methods, 1)} ${this.fullName} `.trim() } -} \ No newline at end of file +} diff --git a/csharp/code-dom/constructor.ts b/csharp/code-dom/constructor.ts new file mode 100644 index 0000000000..34757c57e0 --- /dev/null +++ b/csharp/code-dom/constructor.ts @@ -0,0 +1,20 @@ +import { Method } from "./method"; +import { docComment, EOL, CommaChar, indent } from "#common/text-manipulation"; +import { Class } from "./class"; + +export class Constructor extends Method { + constructor(protected containingClass: Class, objectIntializer?: Partial) { + super(containingClass.name); + this.apply(objectIntializer); + } + + public get declaration(): string { + const parameterDeclaration = this.parameters.joinWith(p => p.declaration, CommaChar); + + return ` +${this.summaryDocumentation} +${this.parameterDocumentation} +${this.accessModifier} ${this.name}(${parameterDeclaration}) +`.trim(); + } +} \ No newline at end of file diff --git a/csharp/code-dom/doc-comments.ts b/csharp/code-dom/doc-comments.ts new file mode 100644 index 0000000000..1d5bc1b87b --- /dev/null +++ b/csharp/code-dom/doc-comments.ts @@ -0,0 +1,18 @@ +import { EOL } from "#common/text-manipulation"; + +export const summary = (text: string): string => xmlize("summary", text); + + +export function xmlize(element: string, text: string): string { + if (text) { + if (text.length < 80 && text.indexOf(EOL) === -1) { + return `<${element}>${text.trim()}`; + } + return ` +<${element}> +${text} + +`.trim(); + } + return text; +} \ No newline at end of file diff --git a/csharp/code-dom/expression.ts b/csharp/code-dom/expression.ts index bd08f11ecf..60a4942dfd 100644 --- a/csharp/code-dom/expression.ts +++ b/csharp/code-dom/expression.ts @@ -1,3 +1,10 @@ export interface Expression { value: string; +} + +export class StringExpression implements Expression { + public value: string; + constructor(value: string) { + this.value = `@"${value.replace(/"/g, '""')}"`; + } } \ No newline at end of file diff --git a/csharp/code-dom/field.ts b/csharp/code-dom/field.ts index fa97ebc984..6238dde452 100644 --- a/csharp/code-dom/field.ts +++ b/csharp/code-dom/field.ts @@ -1,10 +1,16 @@ import { TypeDeclaration } from "./type-declaration"; import { Expression } from "#csharp/code-dom/expression"; +import { AccessModifier } from "#csharp/code-dom/access-modifier"; +import { Initializer } from "#csharp/code-dom/initializer"; -export class Field implements Expression { - public visibility = "public"; +export class Field extends Initializer implements Expression { + public visibility: AccessModifier = AccessModifier.Public; + public isStatic: boolean = false; + public description: string = ""; - constructor(public name: string, public type: TypeDeclaration) { + constructor(public name: string, public type: TypeDeclaration, objectInitializer?: Partial) { + super(); + this.apply(objectInitializer); } public get declaration(): string { @@ -14,4 +20,17 @@ export class Field implements Expression { public get value(): string { return `${this.name}`; } -} \ No newline at end of file +} + +export class ConstantField extends Field { + constructor(name: string, type: TypeDeclaration, public valueExpression: Expression, objectInitializer?: Partial) { + super(name, type); + this.apply(objectInitializer); + } + + public get declaration(): string { + const stat = this.isStatic ? " static " : " "; + return `${this.visibility}${stat}${this.type.use} ${this.name} = ${this.valueExpression.value};` + } +} + diff --git a/csharp/code-dom/initializer.ts b/csharp/code-dom/initializer.ts new file mode 100644 index 0000000000..1f0e64a21d --- /dev/null +++ b/csharp/code-dom/initializer.ts @@ -0,0 +1,12 @@ +export class Initializer { + + protected apply(initializer?: Partial) { + if (initializer) { + for (const i in (initializer)) { + //if ((initializer)[i]) { + (this)[i] = (initializer)[i] + //}; + } + } + } +} \ No newline at end of file diff --git a/csharp/code-dom/interface-property.ts b/csharp/code-dom/interface-property.ts index 809fe9dffd..f80fcc1b6b 100644 --- a/csharp/code-dom/interface-property.ts +++ b/csharp/code-dom/interface-property.ts @@ -1,13 +1,15 @@ import { TypeDeclaration } from "./type-declaration"; import { AccessModifier, highestAccess } from "#csharp/code-dom/access-modifier"; import { Property } from "./property"; +import { Interface } from "#csharp/code-dom/interface"; export class InterfaceProperty extends Property { public readVisibility = AccessModifier.Public; public writeVisibility = AccessModifier.Public; - constructor(name: string, type: TypeDeclaration) { + constructor(name: string, type: TypeDeclaration, objectInitializer?: Partial) { super(name, type); + this.apply(objectInitializer); } public get declaration(): string { diff --git a/csharp/code-dom/interface.ts b/csharp/code-dom/interface.ts index 66de52858b..c491e4c3d1 100644 --- a/csharp/code-dom/interface.ts +++ b/csharp/code-dom/interface.ts @@ -3,8 +3,9 @@ import { comment, docCommentPrefix, sortByName, indent, EOL } from "#common/text import { Namespace } from "./namespace"; export class Interface extends Type { - constructor(parent: Namespace, name: string, interfaces = new Array(), genericParameters = new Array(), where?: string) { - super(parent, name, interfaces, genericParameters, where); + constructor(parent: Namespace, name: string, objectIntializer?: Partial) { + super(parent, name); + this.apply(objectIntializer); parent.addInterface(this); } diff --git a/csharp/code-dom/method.ts b/csharp/code-dom/method.ts index 91cda3ec6a..64003e8f45 100644 --- a/csharp/code-dom/method.ts +++ b/csharp/code-dom/method.ts @@ -4,16 +4,21 @@ import { TypeDeclaration } from "./type-declaration"; import * as mscorlib from "./mscorlib"; import { AccessModifier } from "#csharp/code-dom/access-modifier"; import { docCommentPrefix, comment, indent, EOL, CommaChar, join, joinComma, docComment } from "#common/text-manipulation"; +import { xmlize, summary } from "#csharp/code-dom/doc-comments"; export class Method extends Statements { - private parameters = new Array(); + public parameters = new Array(); public accessModifier = AccessModifier.Public; public isAsync = false; + public isOverride = false; + public isNew = false; public description: string = ""; + public isStatic: boolean = false; - constructor(public name: string, private returnType: TypeDeclaration = mscorlib.Void) { + constructor(public name: string, protected returnType: TypeDeclaration = mscorlib.Void, objectIntializer?: Partial) { super(); + this.apply(objectIntializer); } public addParameter(parameter: Parameter): Parameter { @@ -21,25 +26,57 @@ export class Method extends Statements { return parameter; } + protected get summaryDocumentation(): string { + return docComment(summary(this.description)); + } + + protected get parameterDocumentation(): string { + return docComment(this.parameters.joinWith(p => p.comment, EOL)); + } + public get declaration(): string { - const description = docComment(this.description); - const params = docComment(this.parameters.joinWith(p => p.comment, EOL)); const parameterDeclaration = this.parameters.joinWith(p => p.declaration, CommaChar); - + const overrideOrNew = this.isOverride ? " override " : this.isNew ? " new " : " "; + const stat = this.isStatic ? " static" : ""; const asynch = this.isAsync ? "async " : ""; return ` -${description} -${params} -${this.accessModifier} ${asynch}${this.returnType.use} ${this.name}(${parameterDeclaration}) { -${indent(super.implementation, 1)} -} +${this.summaryDocumentation} +${this.parameterDocumentation} +${this.accessModifier}${stat}${overrideOrNew}${asynch}${this.returnType.use} ${this.name}(${parameterDeclaration}) `.trim(); } public get implementation(): string { return ` ${this.declaration} +{ +${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 659091352b..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 ``; } @@ -28,8 +24,9 @@ export const Float: TypeDeclaration = new LibraryType("float"); export const Date: TypeDeclaration = new LibraryType("DateTime"); 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/namespace.ts b/csharp/code-dom/namespace.ts index 69d7de194e..077b9bd6a8 100644 --- a/csharp/code-dom/namespace.ts +++ b/csharp/code-dom/namespace.ts @@ -4,8 +4,9 @@ import { Interface } from "./interface"; import { Class } from "./class"; import { Import } from "./import"; import { Project } from "./project"; +import { Initializer } from "#csharp/code-dom/initializer"; -export class Namespace { +export class Namespace extends Initializer { private usings = new Array(); private classes = new Array(); private interfaces = new Array(); @@ -13,28 +14,39 @@ export class Namespace { private namespaces = new Array(); public header: string = ""; - constructor(public name: string, protected parent?: Project | Namespace) { - + constructor(public name: string, protected parent?: Project | Namespace, objectInitializer?: Partial) { + super(); + this.apply(objectInitializer); } public addUsing(using: Import): Import { - this.usings.push(using); + if (this.usings.indexOf(using) === -1) { + this.usings.push(using); + } return using; } public addClass(c: Class): Class { - this.classes.push(c); + if (this.classes.indexOf(c) === -1) { + this.classes.push(c); + } return c; } public addInterface(i: Interface): Interface { - this.interfaces.push(i); + if (this.interfaces.indexOf(i) === -1) { + this.interfaces.push(i); + } return i; } public addDelegate(delegate: Delegate): Delegate { - this.delegates.push(delegate); + if (this.delegates.indexOf(delegate) === -1) { + this.delegates.push(delegate); + } return delegate; } public addNamespace(n: Namespace): Namespace { - this.namespaces.push(n); + if (this.namespaces.indexOf(n) === -1) { + this.namespaces.push(n); + } return n; } diff --git a/csharp/code-dom/operator.ts b/csharp/code-dom/operator.ts new file mode 100644 index 0000000000..912e44c827 --- /dev/null +++ b/csharp/code-dom/operator.ts @@ -0,0 +1,21 @@ +import { Method } from "./method"; +import { docComment, EOL, CommaChar, indent } from "#common/text-manipulation"; +import { Class } from "./class"; + +export class Operator extends Method { + constructor(name: string, objectIntializer?: Partial) { + super(name); + this.apply(objectIntializer); + } + + public get declaration(): string { + const parameterDeclaration = this.parameters.joinWith(p => p.declaration, CommaChar); + const overrideOrNew = this.isOverride ? " override " : this.isNew ? " new " : " "; + const stat = this.isStatic ? " static" : ""; + return ` +${this.summaryDocumentation} +${this.parameterDocumentation} +${this.accessModifier}${stat}${overrideOrNew} ${this.name}(${parameterDeclaration}) +`.trim(); + } +} \ 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 58bb3e180a..4cd495b7a2 100644 --- a/csharp/code-dom/parameter.ts +++ b/csharp/code-dom/parameter.ts @@ -1,18 +1,25 @@ 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 implements Expression { +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) { - + public constructor(public name: string, public type: TypeDeclaration, objectInitializer?: Partial) { + super(); + this.apply(objectInitializer); } public get comment() { 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/policy.ts b/csharp/code-dom/policy.ts deleted file mode 100644 index 57d9c7d019..0000000000 --- a/csharp/code-dom/policy.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class Policy { - -} - -export class DefaultPolicy extends Policy { - -} \ No newline at end of file diff --git a/csharp/code-dom/project.ts b/csharp/code-dom/project.ts index 89f4781ebe..989f5fcb26 100644 --- a/csharp/code-dom/project.ts +++ b/csharp/code-dom/project.ts @@ -1,9 +1,12 @@ import { Namespace } from "./namespace"; import { all } from "#common/text-manipulation"; +import { Initializer } from "#csharp/code-dom/initializer"; -export class Project { +export class Project extends Initializer { private namespaces = new Array(); - constructor() { + constructor(objectInitializer?: Partial) { + super(); + this.apply(objectInitializer); } public addNamespace(n: Namespace): Namespace { diff --git a/csharp/code-dom/property.ts b/csharp/code-dom/property.ts index b0a0609157..2668c7bbbb 100644 --- a/csharp/code-dom/property.ts +++ b/csharp/code-dom/property.ts @@ -1,17 +1,20 @@ import { TypeDeclaration } from "./type-declaration"; import { AccessModifier, highestAccess } from "#csharp/code-dom/access-modifier"; import { Expression } from "#csharp/code-dom/expression"; +import { Initializer } from "#csharp/code-dom/initializer"; -export class Property implements Expression { +export class Property extends Initializer implements Expression { public readVisibility = AccessModifier.Public; public writeVisibility = AccessModifier.Public; + public isStatic: boolean = false; public get visibility(): AccessModifier { return highestAccess(this.readVisibility, this.writeVisibility); } - constructor(public name: string, public type: TypeDeclaration) { - + constructor(public name: string, public type: TypeDeclaration, objectInitializer?: Partial) { + super(); + this.apply(objectInitializer); } protected get getter(): string { @@ -22,7 +25,8 @@ export class Property implements Expression { }; public get declaration(): string { - return `${this.visibility} ${this.type.use} ${this.name} { ${this.getter}; ${this.setter}; }` + const stat = this.isStatic ? " static " : " "; + return `${this.visibility}${stat}${this.type.use} ${this.name} { ${this.getter}; ${this.setter}; }` } public get value(): string { return `${this.name}`; diff --git a/csharp/code-dom/statements/case.ts b/csharp/code-dom/statements/case.ts index 59c21ed556..04e937bb53 100644 --- a/csharp/code-dom/statements/case.ts +++ b/csharp/code-dom/statements/case.ts @@ -2,8 +2,9 @@ import { OneOrMoreStatements, Statements } from "#csharp/code-dom/statements/sta import { indent } from "#common/text-manipulation"; export class Case extends Statements { - constructor(private value: string, body: OneOrMoreStatements) { + constructor(private value: string, body: OneOrMoreStatements, objectInitializer?: Partial) { super(body); + this.apply(objectInitializer); } protected get statementsImplementation(): string { @@ -19,8 +20,9 @@ ${indent('break')}; } export class DefaultCase extends Case { - constructor(body: OneOrMoreStatements) { + constructor(body: OneOrMoreStatements, objectInitializer?: Partial) { super("", body); + this.apply(objectInitializer); } public get implementation(): string { @@ -34,8 +36,9 @@ ${indent('break')}; export class TerminalDefaultCase extends Case { - constructor(body: OneOrMoreStatements) { + constructor(body: OneOrMoreStatements, objectInitializer?: Partial) { super("", body); + this.apply(objectInitializer); } public get implementation(): string { diff --git a/csharp/code-dom/statements/finally.ts b/csharp/code-dom/statements/finally.ts index 96af594367..8f4ad596ec 100644 --- a/csharp/code-dom/statements/finally.ts +++ b/csharp/code-dom/statements/finally.ts @@ -2,8 +2,9 @@ import { Statements, OneOrMoreStatements } from "#csharp/code-dom/statements/sta import { indent } from "#common/text-manipulation"; export class Finally extends Statements { - constructor(statements: OneOrMoreStatements) { + constructor(statements: OneOrMoreStatements, objectInitializer?: Partial) { super(statements); + this.apply(objectInitializer); } public get implementation(): string { diff --git a/csharp/code-dom/statements/statement.ts b/csharp/code-dom/statements/statement.ts index 02c8e95cbf..df68d28818 100644 --- a/csharp/code-dom/statements/statement.ts +++ b/csharp/code-dom/statements/statement.ts @@ -1,7 +1,9 @@ import { EOL, indent } from "#common/text-manipulation"; 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; @@ -10,20 +12,32 @@ export interface Statement { export function isStatement(object: OneOrMoreStatements): object is Statement { return (object).implementation ? true : false; } -export class Statements implements Statement { +export class Statements extends Initializer implements Statement { protected statements = new Array(); - constructor(statements?: OneOrMoreStatements) { + constructor(statements?: OneOrMoreStatements, objectIntializer?: Partial) { + super(); if (statements) { this.add(statements); } + 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') { @@ -35,13 +49,35 @@ export class Statements 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; } public get implementation(): string { - return ` -${this.statements.joinWith(each => each.implementation, EOL)} -`.trim(); + return `${this.statements.joinWith(each => each.implementation, EOL)}`.trim(); } } diff --git a/csharp/code-dom/statements/switch.ts b/csharp/code-dom/statements/switch.ts index 0428521ee1..ff74e13d1b 100644 --- a/csharp/code-dom/statements/switch.ts +++ b/csharp/code-dom/statements/switch.ts @@ -2,10 +2,11 @@ import { Statement } from "#csharp/code-dom/statements/statement"; import { Expression } from "#csharp/code-dom/expression"; import { Case } from "#csharp/code-dom/statements/case"; import { indent, EOL } from "#common/text-manipulation"; +import { Initializer } from "#csharp/code-dom/initializer"; export type OneOrMoreCases = (() => Iterable) | Iterable | Case; -export class Switch implements Statement { +export class Switch extends Initializer implements Statement { protected caseStatements = new Array(); public get implementation(): string { @@ -15,7 +16,9 @@ switch( ${this.expression.value} ) ${indent(this.caseStatements.map(each => each.implementation).join(EOL))} }` } - constructor(protected expression: Expression, cases: OneOrMoreCases) { + constructor(protected expression: Expression, cases: OneOrMoreCases, objectInitializer?: Partial) { + super(); + this.apply(objectInitializer); this.add(cases); } public add(cases: OneOrMoreCases) { diff --git a/csharp/code-dom/statements/try.ts b/csharp/code-dom/statements/try.ts index 9e031caf7b..6e4e1c67c2 100644 --- a/csharp/code-dom/statements/try.ts +++ b/csharp/code-dom/statements/try.ts @@ -2,8 +2,9 @@ import { Statements, OneOrMoreStatements } from "#csharp/code-dom/statements/sta import { indent } from "#common/text-manipulation"; export class Try extends Statements { - constructor(statements: OneOrMoreStatements) { + constructor(statements: OneOrMoreStatements, objectInitializer?: Partial) { super(statements); + this.apply(objectInitializer); } public get implementation(): string { return ` diff --git a/csharp/code-dom/statements/using.ts b/csharp/code-dom/statements/using.ts index 820023ebb1..257fe7f68b 100644 --- a/csharp/code-dom/statements/using.ts +++ b/csharp/code-dom/statements/using.ts @@ -2,8 +2,9 @@ import { Statements, Statement, OneOrMoreStatements } from "#csharp/code-dom/sta import { indent } from "#common/text-manipulation"; export class Using extends Statements { - constructor(private usingStatement: Statement | string, statements: OneOrMoreStatements) { + constructor(private usingStatement: Statement | string, statements: OneOrMoreStatements, objectInitializer?: Partial) { super(statements); + this.apply(objectInitializer); } public get implementation(): string { return ` diff --git a/csharp/code-dom/struct.ts b/csharp/code-dom/struct.ts new file mode 100644 index 0000000000..ff8822b3e2 --- /dev/null +++ b/csharp/code-dom/struct.ts @@ -0,0 +1,10 @@ +import { Class } from "#csharp/code-dom/class"; +import { Namespace } from "#csharp/code-dom/namespace"; + +export class Struct extends Class { + constructor(namespace: Namespace, name: string, public parent?: Struct, objectIntializer?: Partial) { + super(namespace, name); + this.apply(objectIntializer); + this.classOrStruct = "struct"; + } +} 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/code-dom/type.ts b/csharp/code-dom/type.ts index 1d224f9244..b68258c17c 100644 --- a/csharp/code-dom/type.ts +++ b/csharp/code-dom/type.ts @@ -4,15 +4,20 @@ import { Property } from "./property"; import { TypeDeclaration } from "./type-declaration"; import { AccessModifier } from "#csharp/code-dom/access-modifier"; import { Namespace } from "#csharp/code-dom/namespace"; +import { Initializer } from "#csharp/code-dom/initializer"; -export class Type implements TypeDeclaration { +export class Type extends Initializer implements TypeDeclaration { public description: string = ""; public methods = new Array(); public properties = new Array(); + public genericParameters = new Array(); + public where?: string; + public interfaces = new Array(); public accessModifier = AccessModifier.Public; - constructor(public parent: Namespace, public name: string, public interfaces = new Array(), public genericParameters = new Array(), public where?: string) { - + constructor(public namespace: Namespace, public name: string, objectIntializer?: Partial) { + super(); + this.apply(objectIntializer); } public get allProperties(): Array { @@ -28,7 +33,7 @@ export class Type implements TypeDeclaration { } public get fullName(): string { - return `${this.parent.fullName}.${this.name}${this.genericDeclaration}`; + return `${this.namespace.fullName}.${this.name}${this.genericDeclaration}`; } public get use(): string { diff --git a/csharp/inferrer.ts b/csharp/inferrer.ts new file mode 100644 index 0000000000..1e6c972d3c --- /dev/null +++ b/csharp/inferrer.ts @@ -0,0 +1,13 @@ +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"; + +export async function process(service: Host) { + return await processCodeModel(inferStuff, service); +} + +async function inferStuff(model: Model, service: Host): Promise { + return model; +} \ No newline at end of file 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 14e4ffcdb3..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 { @@ -48,7 +47,7 @@ export async function process(service: Host) { const modelState = new State(service, model, filename); - const project = await Project.create(modelState); + const project = new Project(modelState); await project.writeFiles(async (filename, content) => await service.WriteFile(filename, content, undefined)); diff --git a/csharp/lowlevel-generator/model/backing-field.ts b/csharp/lowlevel-generator/model/backing-field.ts deleted file mode 100644 index 9fdfaf1b37..0000000000 --- a/csharp/lowlevel-generator/model/backing-field.ts +++ /dev/null @@ -1,20 +0,0 @@ -import * as codeModel from "#remodeler/code-model"; -import { State } from "../generator"; - -import { Field } from "#csharp/code-dom/field"; -import { ModelClass } from "./class"; -import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; - -export class BackingField extends Field { - protected constructor(name: string, type: TypeDeclaration) { - super(name, type); - } - - public static async create(parent: ModelClass, name: string, typeDecl: TypeDeclaration, state: State) { - const backingField = new BackingField(name, typeDecl); - backingField.visibility = "private"; - - parent.addField(backingField); - return backingField; - } -} \ No newline at end of file diff --git a/csharp/lowlevel-generator/model/class.ts b/csharp/lowlevel-generator/model/class.ts index cf7e3fc061..b7c3f2dae7 100644 --- a/csharp/lowlevel-generator/model/class.ts +++ b/csharp/lowlevel-generator/model/class.ts @@ -9,31 +9,39 @@ import { State } from "../generator"; import { Schema } from "#remodeler/code-model"; import { Namespace } from "#csharp/code-dom/namespace"; import { Interface } from "#csharp/code-dom/interface"; -import { BackingField } from "./backing-field"; 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 { - protected constructor(parent: Namespace, name: string, state: State) { - super(parent, name); - } + public serializeStatements = new Statements(); + private validateMethod?: Method; - public static async create(parent: Namespace, schema: Schema, state: State): Promise { - if (schema.details.privateData["class-implementation"]) { - // if we've already created this type, return the implementation of it. - return schema.details.privateData["class-implementation"]; - } - const modelClass = new ModelClass(parent, schema.details.name, state); + 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"] = modelClass; + privateData.classImplementation = this; // track the namespace we've used. - schema.details.namespace = parent.fullName; + schema.details.namespace = namespace.fullName; + + // mark it as json serializable + this.interfaces.push(IJsonSerializable); // create an interface for this model class - const modelInterface = await ModelInterface.create(parent, schema, state); - modelClass.interfaces.push(modelInterface); + const modelInterface = privateData.interfaceImplementation || new ModelInterface(namespace, schema, this, state); + this.interfaces.push(modelInterface); // handle s // add an 'implements' for the interface for the allOf. @@ -41,20 +49,20 @@ export class ModelClass extends Class { const aSchema = schema.allOf[allOf]; const aState = state.path("allOf"); - const td = await 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); // add a field for the inherited values - const backingField = await BackingField.create(modelClass, `_allof_${allOf}`, td, aState); + const backingField = this.addField(new Field(`_allof_${allOf}`, td, { visibility: AccessModifier.Private })); // now, create proxy properties for the members - for (const each of iface.properties) { - await ProxyProperty.create(modelClass, backingField, each, state); - } + 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. @@ -64,7 +72,9 @@ export class ModelClass extends Class { for (const propertyName in schema.properties) { const property = schema.properties[propertyName]; - ModelProperty.create(modelClass, 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) { @@ -77,12 +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)); - // add constructors + // const propVal = this.properties.joinWith(each => (each).validationStatement, EOL); + // const propValPresence = this.properties.joinWith(each => (each).validatePresenceStatement, EOL); - // add serialization + 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); + } + + } - return modelClass; + 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 8767925156..9db129e4c6 100644 --- a/csharp/lowlevel-generator/model/interface-property.ts +++ b/csharp/lowlevel-generator/model/interface-property.ts @@ -8,24 +8,8 @@ import { ToDo } from "#csharp/code-dom/mscorlib"; import { Interface } from "#csharp/code-dom/interface"; export class ModelInterfaceProperty extends InterfaceProperty { - protected constructor(name: string, type: TypeDeclaration) { - super(name, type); - } - - public static async create(parent: ModelInterface, property: codeModel.PropertyReference, state: State) { - // get the typeDeclaration for the schema - const typeDecl = await state.project.modelsNamespace.resolveTypeDeclaration(property.schema, state.path("schema")); - - if (typeDecl != null) { - // create the property. - const modelProperty = new ModelInterfaceProperty(property.details.name, typeDecl); - - // add the new property to the parent - parent.addProperty(modelProperty); - - // return the property - return modelProperty; - } - return null; + constructor(parent: ModelInterface, property: codeModel.PropertyReference, state: State, objectInitializer?: Partial) { + 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 7a5421d151..74d5f4efa9 100644 --- a/csharp/lowlevel-generator/model/interface.ts +++ b/csharp/lowlevel-generator/model/interface.ts @@ -3,26 +3,35 @@ 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 { - protected constructor(parent: Namespace, name: string, state: State) { - super(parent, `I${name}`); +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); } - public static async create(parent: Namespace, schema: Schema, state: State): Promise { - if (schema.details.privateData["interface-implementation"]) { - // if we've already created this type, return the implementation of it. - return schema.details.privateData["interface-implementation"]; - } - const modelInterface = new ModelInterface(parent, schema.details.name, state); - schema.details.privateData["interface-implementation"] = modelInterface; + constructor(parent: Namespace, schema: Schema, public classImplementation: ModelClass, state: State, objectInitializer?: Partial) { + super(parent, `I${schema.details.name}`); + this.apply(objectInitializer); - for (const propertyName in schema.properties) { - const property = schema.properties[propertyName]; + schema.details.privateData.interfaceImplementation = this; - ModelInterfaceProperty.create(modelInterface, property, state.path('properties', propertyName)); + for (const propertyName in schema.properties) { + this.addProperty(new ModelInterfaceProperty(this, schema.properties[propertyName], state.path('properties', propertyName))); } - return modelInterface; + } + } \ No newline at end of file diff --git a/csharp/lowlevel-generator/model/namespace.ts b/csharp/lowlevel-generator/model/namespace.ts index f8ef6a5457..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,24 +22,22 @@ 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 { - protected constructor(name: string, parent: Namespace, private schemas: Dictionary, private state: State) { - super(name, parent); - } + constructor(parent: Namespace, private schemas: Dictionary, private state: State, objectInitializer?: Partial) { + super("Models", parent); + this.apply(objectInitializer); - public static async create(parent: Namespace, schemas: Dictionary, state: State): Promise { - const modelsNamespace = new ModelsNamespace("Models", parent, schemas, state); - state.project.addNamespace(modelsNamespace); - return await modelsNamespace.init(); - } + // special case... hook this up before we get anywhere. + state.project.modelsNamespace = this; - public async init(): Promise { - for (const schemaName in this.schemas) { + for (const schemaName in schemas) { const schema = this.schemas[schemaName]; const state = this.state.path(schemaName); @@ -47,18 +45,21 @@ export class ModelsNamespace extends Namespace { if (validation.objectWithFormat(schema, state)) { continue; } - - await this.resolveTypeDeclaration(schema, state); + this.resolveTypeDeclaration(schema, true, state); } - - return this; } private static INVALID = null; - public async resolveTypeDeclaration(schema: Schema, state: State): Promise { + + 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 @@ -68,24 +69,138 @@ 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 await this.createWildcardObject(schema, state); + return privateData.typeDeclaration = new UntypedWildcard(); } else { // the object is a wildcard for all key/-value pairs - const wcSchema = await this.resolveTypeDeclaration(schema.additionalProperties, state.path("additionalProperties")); + const wcSchema = this.resolveTypeDeclaration(schema.additionalProperties, false, state.path("additionalProperties")); - return await this.createWildcardForSchema(schema, wcSchema, state); + 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 = await ModelClass.create(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); + 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, 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); + privateData.typeDeclaration = privateData.interfaceInplementation; + return privateData.typeDeclaration; case JsonType.String: - // console.error(`schema 'string': ${schema.details.name} `); switch (schema.format) { case StringFormat.Base64Url: case StringFormat.Byte: @@ -93,64 +208,69 @@ export class ModelsNamespace extends Namespace { return ModelsNamespace.INVALID; } // member should be byte array - // on wire format should be base64url - return await 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 await 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 await 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 await 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 await this.createDateTime1123(schema, state); + return privateData.typeDeclaration = new DateTime1123(); case StringFormat.Duration: if (validation.schemaHasEnum(schema, state)) { return ModelsNamespace.INVALID; } - return await this.createDuration(schema, state); + return privateData.typeDeclaration = new Duration(); case StringFormat.Uuid: if (validation.hasXmsEnum(schema, state)) { return ModelsNamespace.INVALID; } - return await 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 await 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); @@ -161,19 +281,19 @@ export class ModelsNamespace extends Namespace { if (validation.hasXmsEnum(schema, state)) { return ModelsNamespace.INVALID; } - return await this.createBoolean(schema, state); + return privateData.typeDeclaration = required ? new Boolean() : new NullableBoolean(); case JsonType.Integer: if (validation.hasXmsEnum(schema, state)) { return ModelsNamespace.INVALID; } - return await this.createInteger(schema, state); + return privateData.typeDeclaration = new Integer(); case JsonType.Number: if (validation.hasXmsEnum(schema, state)) { return ModelsNamespace.INVALID; } - return await this.createFloatingpoint(schema, state); + return privateData.typeDeclaration = new Float(); case JsonType.Array: if (validation.hasXmsEnum(schema, state)) { @@ -182,12 +302,13 @@ export class ModelsNamespace extends Namespace { if (validation.arrayMissingItems(schema, state)) { return ModelsNamespace.INVALID; } - const aSchema = await this.resolveTypeDeclaration(schema.items, state.path("items")); - return await 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: @@ -197,64 +318,17 @@ export class ModelsNamespace extends Namespace { return ModelsNamespace.INVALID; } - async createWildcardObject(schema: Schema, state: State): Promise { - return schema.details.privateData["type-declaration"] = new UntypedWildcard(); - } - async createWildcardForSchema(schema: Schema, typeDecl: TypeDeclaration, state: State): Promise { - return schema.details.privateData["type-declaration"] = new Wildcard(typeDecl); - } - async createByteArray(schema: Schema, state: State): Promise { - return schema.details.privateData["type-declaration"] = new ByteArray(); - } - async createEnum(schema: Schema, state: State): Promise { - // 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"] = await EnumClass.create(schema, state); - } - async createStream(schema: Schema, state: State): Promise { - throw new Error("Method not implemented."); - } - async createBoolean(schema: Schema, state: State): Promise { - return schema.details.privateData["type-declaration"] = new Boolean(); - } - async createFloatingpoint(schema: Schema, state: State): Promise { - return schema.details.privateData["type-declaration"] = new Float(); - } - async createArray(schema: Schema, typeDecl: TypeDeclaration, state: State): Promise { - return schema.details.privateData["type-declaration"] = new ArrayOf(typeDecl); - } - async createInteger(schema: Schema, state: State): Promise { - return schema.details.privateData["type-declaration"] = new Integer(); - } - async createDate(schema: Schema, state: State): Promise { - return schema.details.privateData["type-declaration"] = new Date(); - } - async createDateTime(schema: Schema, state: State): Promise { - return schema.details.privateData["type-declaration"] = new DateTime(); - } - async createDateTime1123(schema: Schema, state: State): Promise { - return schema.details.privateData["type-declaration"] = new DateTime1123(); - } - async createDuration(schema: Schema, state: State): Promise { - return schema.details.privateData["type-declaration"] = new Duration(); - } - async createUuid(schema: Schema, state: State): Promise { - return schema.details.privateData["type-declaration"] = new Uuid(); - } - async createString(schema: Schema, state: State): Promise { - // 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); - } - async createChar(schema: Schema, state: State): Promise { - // 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 8710aea7de..c47a6a22e1 100644 --- a/csharp/lowlevel-generator/model/property.ts +++ b/csharp/lowlevel-generator/model/property.ts @@ -1,31 +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 { - protected constructor(name: string, type: TypeDeclaration) { - super(name, type); + constructor(parent: ModelClass, property: codeModel.PropertyReference, state: State, objectInitializer?: Partial) { + super(property.details.name, state.project.modelsNamespace.resolveTypeDeclaration(property.schema, property.details.required, state.path("schema"))); + this.apply(objectInitializer); } - public static async create(parent: ModelClass, property: codeModel.PropertyReference, state: State) { - // get the typeDeclaration for the schema - const typeDecl = await state.project.modelsNamespace.resolveTypeDeclaration(property.schema, state.path("schema")); - - if (typeDecl != null) { - // create the property. - const modelProperty = new ModelProperty(property.details.name, typeDecl); - - // add the new property to the parent - parent.addProperty(modelProperty); - - // return the property - return modelProperty; - } - return null; + 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/model/proxy-property.ts b/csharp/lowlevel-generator/model/proxy-property.ts index 639a0b7bd8..2ea29f1358 100644 --- a/csharp/lowlevel-generator/model/proxy-property.ts +++ b/csharp/lowlevel-generator/model/proxy-property.ts @@ -2,24 +2,19 @@ import { Property } from "#csharp/code-dom/property"; import { TypeDeclaration } from "#csharp/code-dom/type-declaration"; import { ModelClass } from "./class"; import { State } from "../generator"; -import { BackingField } from "./backing-field"; +import { Field } from "#csharp/code-dom/field"; export class ProxyProperty extends Property { - protected constructor(name: string, type: TypeDeclaration, protected backingField: BackingField, protected property: Property) { - super(name, type); - } - - public static async create(parent: ModelClass, backingField: BackingField, property: Property, state: State): Promise { - const pp = new ProxyProperty(property.name, property.type, backingField, property); - parent.addProperty(pp); - return pp; + constructor(protected backingFieldObject: Field, protected backingFieldProperty: Property, state: State, objectInitializer?: Partial) { + super(backingFieldProperty.name, backingFieldProperty.type); + this.apply(objectInitializer); } public get declaration(): string { return ` ${this.visibility} ${this.type.use} ${this.name} { - ${this.getter} { return ${this.backingField.name}.${this.property.name}; } - ${this.setter} { ${this.backingField.name}.${this.property.name} = value; } + ${this.getter} { return ${this.backingFieldObject.name}.${this.backingFieldProperty.name}; } + ${this.setter} { ${this.backingFieldObject.name}.${this.backingFieldProperty.name} = value; } }` } } \ No newline at end of file diff --git a/csharp/lowlevel-generator/operation/api-class.ts b/csharp/lowlevel-generator/operation/api-class.ts index 81a72e650a..6797d653bd 100644 --- a/csharp/lowlevel-generator/operation/api-class.ts +++ b/csharp/lowlevel-generator/operation/api-class.ts @@ -14,30 +14,22 @@ import { ISendAsync } from "../clientruntime"; export class ApiClass extends Class { - private ready: Promise; protected sender: Property; - protected constructor(protected project: Project, name: string, protected state: State) { - super(project.serviceNamespace, 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)); - // finish in async constructor - this.ready = (async () => { - state.model.details.namespace = project.serviceNamespace.fullName; + // remember the namespace for this class. + state.model.details.namespace = namespace.fullName; - // add operations from code model - for (const operationName in state.model.components.operations) { - await OperationMethod.create(this, state.model.components.operations[operationName], state.path('components', 'operations', operationName)); - } + // add operations from code model + for (const operationName in state.model.components.operations) { + this.addMethod(new OperationMethod(this, state.model.components.operations[operationName], state.path('components', 'operations', operationName))); + } - // add constructors - return this; - })(); } - public static create(project: Project, state: State): Promise { - return new ApiClass(project, state.model.details.name, state).ready; - } } diff --git a/csharp/lowlevel-generator/operation/method.ts b/csharp/lowlevel-generator/operation/method.ts index 1173bb1e74..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,123 +16,212 @@ 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 { - private ready: Promise; - protected constructor(name: string, protected parent: Class, protected operation: codemodel.HttpOperation, protected state: State) { - super(name, mscorlib.Task); - // finish in async constructor - this.ready = (async () => { - this.isAsync = true; - - // add parameters - const methodParameters = await Promise.all(this.operation.parameters.map(async (value, index) => await OperationParameter.create(this, value, this.state.path('parameters', index)))); - - this.parent.addMethod(this); - this.description = this.operation.details.description || ""; - - // add body paramter - if (this.operation.requestBody) { - const appjson = this.operation.requestBody.content["application/json"]; - if (appjson && appjson.schema) { - await OperationBodyParameter.create(this, "body", this.operation.requestBody.description || "", appjson.schema, this.state.path('requestBody')); - } - } + constructor(protected parent: Class, protected operation: codemodel.HttpOperation, protected state: State, objectInitializer?: Partial) { + super(operation.details.name, mscorlib.Task); + this.apply(objectInitializer); + this.isAsync = true; - // add response delegate parameters - for (const responseCode in this.operation.responses) { - const response = this.operation.responses[responseCode]; - const rState = this.state.path('responses', responseCode); - const c = response.content["application/json"]; - if (c) { - await CallbackParameter.create(this, `on${responseCode}`, c.schema, this.state); - } else { - await CallbackParameter.create(this, `on${responseCode}`, undefined, this.state); - } - } - // add cancellationToken parameter + // add parameters + const methodParameters = this.operation.parameters.map((value, index) => this.addParameter(new OperationParameter(this, value, this.state.path('parameters', index)))); - const cancellationToken = this.addParameter(new Parameter("cancellationToken", mscorlib.CancellationToken)); + this.description = this.operation.details.description || ""; - // add eventhandler parameter - const listener = this.addParameter(new Parameter("listener", mscorlib.EventListener)); + // add body paramter + let bodyParameter: OperationBodyParameter; - this.add(this.methodBody(methodParameters, listener, cancellationToken)); - return this; - })(); - } + if (this.operation.requestBody) { + const appjson = this.operation.requestBody.content["application/json"]; + if (appjson && appjson.schema) { - public static async create(parent: Class, operation: codemodel.HttpOperation, state: State): Promise { - return new OperationMethod(operation.details.name, parent, operation, state).ready; - } + bodyParameter = new OperationBodyParameter(this, "body", this.operation.requestBody.description || "", appjson.schema, this.operation.requestBody.required, this.state.path('requestBody')); + this.addParameter(bodyParameter); + } + } - private *methodBody(methodParameters: Array, listener: Parameter, cancellationToken: Parameter): Iterable { - const eventListener = new EventListener(listener); - const operation = this.operation; + // add response delegate parameters + const responseMatrix = map(this.operation.responses, (responseCode, response) => { + const rState = this.state.path('responses', responseCode); + 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: [] + } + + ] + } + } - yield `${listener.value}.cancellationToken = ${cancellationToken.value};`; + 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 + }; + }) + }; + }); - yield `// fire event before validation`; - yield eventListener.fire("Validation"); + // add cancellationToken parameter - yield EOL; - yield `// perform parameter validation here`; + const cancellationToken = this.addParameter(new Parameter("cancellationToken", mscorlib.CancellationToken)); - for (const parameter of methodParameters) { - // spit out parameter validation - yield parameter.validate; - } + // add eventhandler parameter + const listener = this.addParameter(new Parameter("listener", EventListenr)); - yield eventListener.fire("AfterValidation"); + // add method implementation... + this.add(function* () { + const eventListener = new EventListener(listener); + yield `${listener.value}.cancellationToken = ${cancellationToken.value};`; - 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 eventListener.fire("RequestCreated", '_request'); + yield `// fire event before validation`; + yield eventListener.fire("Validation"); - yield `System.Net.Http.HttpResponseMessage _response = null;`; - yield new Try(function* () { - // try statements - yield `_response = await this.Sender.SendAsync(_request, ${cancellationToken.value}, ${listener.value});`; - yield eventListener.fire("ResponseCreated", '_response'); + yield EOL; + yield `// perform parameter validation here`; - // add response handlers - yield new Switch({ value: `_response.StatusCode` }, function* () { - 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 { - yield new DefaultCase(function* () { - yield "// on default ... "; - yield EOL; + for (const parameter of methodParameters) { + // spit out parameter validation + yield parameter.validatePresenceStatement; + yield parameter.validationStatement; + } + + // 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(${ClientRuntime.fullName}.Method.${operation.method.capitalize()}, "http://wherever/...")`, function* () { + yield eventListener.fire("RequestCreated", '_request'); + + yield `System.Net.Http.HttpResponseMessage _response = null;`; + yield new Try(function* () { + // 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 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 { + yield new DefaultCase(function* () { + yield "// on default ... "; + 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 ${ClientRuntime.fullName}.UndeclaredResponseException(_response.StatusCode);`; }); } - } - - 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 new Finally(function* () { + yield "// finally statements"; + yield eventListener.fire("Finally", "_request", "_response"); + yield `_response?.Dispose();` }); - }); - yield new Finally(function* () { - yield "// finally statements"; - yield eventListener.fire("Finally", "_request", "_response"); - yield `_response?.Dispose();` }); }); - }); + } + ); + } + } export class EventListener { @@ -151,14 +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});`; } } -/* -export class FireEventWithRequest extends Statement { - -} - -export class HttpStatusCodeCase extends Case { - -}*/ \ No newline at end of file diff --git a/csharp/lowlevel-generator/operation/namespace.ts b/csharp/lowlevel-generator/operation/namespace.ts index 4dcdea1254..3b82b81122 100644 --- a/csharp/lowlevel-generator/operation/namespace.ts +++ b/csharp/lowlevel-generator/operation/namespace.ts @@ -5,15 +5,8 @@ import { ModelsNamespace } from "../model/namespace"; import * as message from "../messages"; export class ServiceNamespace extends Namespace { - protected constructor(name: string, public state: State) { - super(name, state.project); - } - - public static async create(state: State): Promise { - // set the serviceNamespace - const name = await state.service.GetValue("namespace") || "Sample.API"; - - state.project.addNamespace(new ServiceNamespace(name, state)); - return state.project.serviceNamespace; + constructor(public state: State, objectInitializer?: Partial) { + super(state.model.details.namespace || "INVALID.NAMESPACE", state.project); + this.apply(objectInitializer); } } \ No newline at end of file diff --git a/csharp/lowlevel-generator/operation/parameter.ts b/csharp/lowlevel-generator/operation/parameter.ts index 31615c6668..870f4afb01 100644 --- a/csharp/lowlevel-generator/operation/parameter.ts +++ b/csharp/lowlevel-generator/operation/parameter.ts @@ -5,65 +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 { - protected constructor(name: string, type: TypeDeclaration) { - super(name, type); - } - public static async create(parent: Method, param: codemodel.Parameter, state: State): Promise { - if (param.schema) { - const type = await state.project.modelsNamespace.resolveTypeDeclaration(param.schema, state.path('schema')); - const parameter = new OperationParameter(param.details.name, type); - parent.addParameter(parameter); - - parameter.description = param.details.description || ""; + constructor(parent: Method, param: codemodel.Parameter, state: State, objectInitializer?: Partial) { - return parameter; - } - throw Error("NO 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 { - protected constructor(name: string, type: TypeDeclaration) { - super(name, type); + 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 static async create(parent: Method, name: string, description: string, schema: codemodel.Schema, state: State): Promise { - const type = await state.project.modelsNamespace.resolveTypeDeclaration(schema, state.path('schema')); - const parameter = new OperationBodyParameter(name, type); - parent.addParameter(parameter); - - parameter.description = description || schema.details.description || ""; - - return parameter; - + 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 { - protected constructor(name: string, type: TypeDeclaration) { - super(name, type); - } - public static async create(parent: Method, name: string, bodyType: codemodel.Schema | undefined /*, headerType: codemodel.Schema*/, state: State): Promise { - const body = bodyType ? await state.project.modelsNamespace.resolveTypeDeclaration(bodyType, state) || undefinedType : undefinedType; - // const header = await state.project.modelsNamespace.resolveTypeDeclaration(headerType, state) || undefined; - const header = undefinedType; - const onResponseType = new mscorlib.LibraryType(`Microsoft.Rest.OnResponse<${body.use},${header.use}>`); - - const parameter = new CallbackParameter(name, onResponseType); - parent.addParameter(parameter); - - // parameter.description = description || schema.details.description || ""; - - return 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, 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 847cd15162..6f8d6bb470 100644 --- a/csharp/lowlevel-generator/project.ts +++ b/csharp/lowlevel-generator/project.ts @@ -8,77 +8,37 @@ 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 { - protected constructor() { + constructor(protected state: State) { super(); - } - - private _serviceNamespace?: ServiceNamespace; - private _modelsNamespace?: ModelsNamespace; - private _supportNamespace?: SupportNamespace; - - public get serviceNamespace(): ServiceNamespace { - if (this._serviceNamespace) { - return this._serviceNamespace; - } - throw Error("Missing Service Namespace"); - } - - public get modelsNamespace(): ModelsNamespace { - if (this._modelsNamespace) { - return this._modelsNamespace; - } - throw Error("Missing Models Namespace"); - } - - public get supportNamespace(): SupportNamespace { - if (this._supportNamespace) { - return this._supportNamespace; - } - throw Error("Missing Support Namespace"); - } - - - public addNamespace(n: Namespace): Namespace { - if (n instanceof ModelsNamespace) { - this._modelsNamespace = n; - } - if (n instanceof ServiceNamespace) { - this._serviceNamespace = n; - } - if (n instanceof SupportNamespace) { - this._supportNamespace = n; - } - return super.addNamespace(n); - } - - public static async create(state: State): Promise { - const project = new Project(); - state.project = project; - return await project.init(state); - } - - private async init(state: State): Promise { - + state.project = this; // add project namespace - await ServiceNamespace.create(state); + this.addNamespace(this.serviceNamespace = new ServiceNamespace(state)); // add support namespace - await SupportNamespace.create(this.serviceNamespace, state); + this.addNamespace(this.supportNamespace = new SupportNamespace(this.serviceNamespace, state)); // add model classes - await ModelsNamespace.create(this.serviceNamespace, state.model.components.schemas, state.path('components', 'schemas')); - + this.addNamespace(this.modelsNamespace = new ModelsNamespace(this.serviceNamespace, state.model.components.schemas, state.path('components', 'schemas'))); // create API class - await ApiClass.create(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(); - - return this; } + public serviceNamespace: ServiceNamespace; + public modelsNamespace: ModelsNamespace; + public supportNamespace: SupportNamespace; } diff --git a/csharp/lowlevel-generator/support/enum.ts b/csharp/lowlevel-generator/support/enum.ts index d4cd9e5316..ee092b5419 100644 --- a/csharp/lowlevel-generator/support/enum.ts +++ b/csharp/lowlevel-generator/support/enum.ts @@ -3,18 +3,116 @@ import { Project } from "../project"; import { State } from "../generator"; import { Schema } from "#remodeler/code-model"; import { Namespace } from "#csharp/code-dom/namespace"; +import { Interface } from "#csharp/code-dom/interface"; +import { ConstantField } from "#csharp/code-dom/field"; +import { StringExpression } from "#csharp/code-dom/expression"; +import { Constructor } from "#csharp/code-dom/constructor"; +import { Parameter } from "#csharp/code-dom/parameter"; +import { String } from "#csharp/code-dom/mscorlib"; +import { Property } from "#csharp/code-dom/property"; +import { AccessModifier } from "#csharp/code-dom/access-modifier"; +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 Class { - protected constructor(parent: Namespace, name: string, state: State) { - super(parent, name); - } +export class EnumClass extends Struct implements TypeDeclaration { + constructor(schema: Schema, state: State, objectInitializer?: Partial) { + if (!schema.details.enum) { + throw new Error("ENUM AINT XMSENUM"); + } + + super(state.project.supportNamespace, schema.details.enum.name, undefined, { + interfaces: [new Interface(new Namespace("System"), "IEquatable", { + genericParameters: [`${schema.details.enum.name}`] + })], + }); + this.apply(objectInitializer); + + // add known enum values + for (const evalue of schema.details.enum.values) { + const field = this.addField(new ConstantField(evalue.name, this, new StringExpression(evalue.value))); + field.isStatic = true; + field.description = evalue.description; + } + + // add backingField + const backingField = this.addProperty(new Property('value', String)); + backingField.readVisibility = AccessModifier.Private; + backingField.writeVisibility = AccessModifier.Private; + + // add private constructor + const p = new Parameter('underlyingValue', String) + const ctor = this.addMethod(new Constructor(this, { + accessModifier: AccessModifier.Private, + parameters: [p], + })).add(`this.${backingField.value} = ${p.value};`); + + // add toString Method + this.addMethod(new Method("ToString", mscorlib.String, { + isOverride: true, + description: `Returns string representation for ${this.name}` + })).add(`return this.${backingField.value};`) + + // add Equals Method(thistype) + this.addMethod(new Method("Equals", mscorlib.Bool, { + description: `Compares values of enum type ${this.name}`, + parameters: [new Parameter("e", this)] + })).add(`return ${backingField.value}.Equals(e.${backingField.value});`) - public static async create(schema: Schema, state: State): Promise { + // add Equals Method(object) + this.addMethod(new Method("Equals", mscorlib.Bool, { + isOverride: true, + description: `Compares values of enum type ${this.name} (override for Object)`, + parameters: [new Parameter("obj", mscorlib.Object)] + })).add(`return obj is ${this.name} && Equals((${this.name})obj);`) - const result = new EnumClass(state.project.supportNamespace, schema.extensions["x-ms-enum"].name, state); + // add implicit operator(string) + this.addMethod(new Operator(`implicit operator ${this.name}`, { + isStatic: true, + description: `Implicit operator to convert string to ${this.name}`, + parameters: [new Parameter('value', mscorlib.String)] + })).add(`return new ${this.name}(value);`); - // state.model.details.namespace = project.supportNamespace.fullName; + // add implicit operator(thistype) + this.addMethod(new Operator(`implicit operator string`, { + isStatic: true, + description: `Implicit operator to convert ${this.name} to string`, + parameters: [new Parameter('e', this)] + })).add(`return e.${backingField.value};`); - return result; + // add operator == + this.addMethod(new Method(`operator ==`, mscorlib.Bool, { + isStatic: true, + description: `Overriding == operator for enum ${this.name}`, + parameters: [new Parameter('e1', this), new Parameter('e2', this)] + })).add(`return e2.Equals(e1);`); + + // add opeator != + this.addMethod(new Method(`operator !=`, mscorlib.Bool, { + isStatic: true, + description: `Overriding != operator for enum ${this.name}`, + parameters: [new Parameter('e1', this), new Parameter('e2', this)] + })).add(`return !e2.Equals(e1);`); + + // add getHashCode + this.addMethod(new Method(`GetHashCode`, mscorlib.Int, { + 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/support/namespace.ts b/csharp/lowlevel-generator/support/namespace.ts index 31706d1c7e..269eae2198 100644 --- a/csharp/lowlevel-generator/support/namespace.ts +++ b/csharp/lowlevel-generator/support/namespace.ts @@ -2,14 +2,9 @@ import { Namespace } from "#csharp/code-dom/namespace"; import { State } from "../generator"; export class SupportNamespace extends Namespace { - protected constructor(name: string, parent: Namespace, private state: State) { - super(name, parent); + constructor(parent: Namespace, private state: State, objectInitializer?: Partial) { + super("Support", parent); + this.apply(objectInitializer); } - public static async create(parent: Namespace, state: State): Promise { - // set the serviceNamespace - - state.project.addNamespace(new SupportNamespace("Support", parent, state)); - return state.project.supportNamespace; - } } \ No newline at end of file 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/csharp/namer.ts b/csharp/namer.ts index 3833cd8d25..c2adafb1fe 100644 --- a/csharp/namer.ts +++ b/csharp/namer.ts @@ -1,53 +1,54 @@ 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 { deconstruct, fixLeadingNumber, pascalCase, camelCase } from "#common/text-manipulation"; export async function process(service: Host) { - try { - // Get the list of files - const files = await service.ListInputs(); + return await processCodeModel(nameStuffRight, service); +} - // get the openapi document - if (files.length != 1) { - throw new Error("Inputs missing."); - } - - const original = await service.ReadFile(files[0]); +async function nameStuffRight(codeModel: Model, service: Host): Promise { - // deserialize - const codeModel = await deserialize(await service.ReadFile(files[0]), files[0]); + // set the namespace for the service + const serviceNamespace = await service.GetValue("namespace") || "Sample.API"; + codeModel.details.namespace = serviceNamespace; - for (const each in codeModel.components.operations) { - const op = codeModel.components.operations[each]; - const details = op.details; + for (const each in codeModel.components.operations) { + const op = codeModel.components.operations[each]; + const details = op.details; - // operations have pascal cased names - details.name = pascalCase(fixLeadingNumber(deconstruct(details.name))); + // operations have pascal cased names + details.name = pascalCase(fixLeadingNumber(deconstruct(details.name))); - // parameters are camelCased. - for (const p of op.parameters) { - p.details.name = camelCase(fixLeadingNumber(deconstruct(p.details.name))); - } + // parameters are camelCased. + for (const p of op.parameters) { + p.details.name = camelCase(fixLeadingNumber(deconstruct(p.details.name))); } + } - for (const schemaName in codeModel.components.schemas) { - const schema = codeModel.components.schemas[schemaName]; - // schema names are pascalCased - schema.details.name = pascalCase(fixLeadingNumber(deconstruct(schema.details.name)));; + for (const schemaName in codeModel.components.schemas) { + const schema = codeModel.components.schemas[schemaName]; + // schema names are pascalCased + schema.details.name = pascalCase(fixLeadingNumber(deconstruct(schema.details.name)));; - // and so are the propertyNmaes - for (const propertyName in schema.properties) { - const d = schema.properties[propertyName].details - d.name = pascalCase(fixLeadingNumber(deconstruct(d.name))); - } + // and so are the propertyNmaes + for (const propertyName in schema.properties) { + const d = schema.properties[propertyName].details + d.name = pascalCase(fixLeadingNumber(deconstruct(d.name))); } - // output the model - await service.WriteFile("code-model-v2.yaml", serialize(codeModel), undefined/*,"code-model-v2"*/); + // fix enum names + if (schema.details.enum) { + schema.details.enum.name = pascalCase(fixLeadingNumber(deconstruct(schema.details.enum.name))); - } catch (E) { - console.error(E); + // and the value names themselves + for (const value of schema.details.enum.values) { + value.name = pascalCase(fixLeadingNumber(deconstruct(value.name))); + } + } } + + return codeModel; } \ No newline at end of file diff --git a/main.ts b/main.ts index d0fbebad5b..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 "./inferrer/inferrer"; +import { process as csinferrer } from "./csharp/inferrer"; import { CommaChar } from "#common/text-manipulation"; @@ -16,13 +18,15 @@ require('source-map-support').install(); async function main() { const pluginHost = new AutoRestExtension(); + // remodeler extensions pluginHost.Add("remodeler", remodeler); pluginHost.Add("inferrer", inferrer); + + // csharp extensions + pluginHost.Add("csinferrer", csinferrer); pluginHost.Add("csnamer", csnamer); pluginHost.Add("llcsharp", llcsharp); - - await pluginHost.Run(); } 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 55689e1d8f..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); } } } @@ -147,11 +144,25 @@ export enum ImplementationLocation { Alias = "Alias" } +export interface EnumValue { + value: any, + description: string, + name: string +} + +export interface EnumDetails { + modelAsString: boolean; + values: Array; + name: string +} + export interface ClientDetails extends Details { } export interface PropertyDetails extends Details { + required: boolean; + } export interface ParameterDetails extends Details { @@ -159,6 +170,7 @@ export interface ParameterDetails extends Details { } export interface SchemaDetails extends Details { + enum?: EnumDetails; } export interface HttpOperationDetails extends Details { @@ -197,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 = {}; } } @@ -294,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(); } } @@ -339,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; } } @@ -352,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; } @@ -555,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 { @@ -624,7 +645,7 @@ export interface HttpOperation extends Extensions, Implementation; responses: Dictionary>; callbacks: Optional>>; - deprecated?: boolean; + deprecated: Optional; security: Optional>; servers: Optional>; } @@ -664,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/interpretations.ts b/remodeler/interpretations.ts index 41ad1303c5..7d93c9b5b9 100644 --- a/remodeler/interpretations.ts +++ b/remodeler/interpretations.ts @@ -1,8 +1,14 @@ import * as OpenAPI from "./oai3"; -import { ImplementationLocation, Server, ExternalDocumentation, MediaType } from "./code-model"; +import { ImplementationLocation, Server, ExternalDocumentation, MediaType, EnumDetails, EnumValue } from "./code-model"; import { getExtensionProperties, clone, Dictionary } from "./common"; import { Remodeler } from "./remodeler"; +interface XMSEnum { + modelAsString?: boolean; + values: [{ value: any, description?: string, name?: string }]; + name: string +} + export function getName(defaultValue: string, original: OpenAPI.Extensions) { return typeof (original["x-ms-client-name"]) === "string" ? original["x-ms-client-name"] : defaultValue; @@ -27,8 +33,30 @@ export function getParameterImplementationLocation(defaultValue: ImplementationL } return defaultValue; } -export function getEnumDefinition() { - +export function getEnumDefinition(original: OpenAPI.Schema): EnumDetails | undefined { + const xmse = original["x-ms-enum"]; + if (xmse && original.enum) { + return { + name: xmse.name, + values: xmse.values ? + xmse.values.map((each) => { + return { + description: each.description || '', + name: each.name || '${each.value}', + value: each.value + }; + }) : + original.enum.map(each => { + return { + description: '', + name: each, + value: each + }; + }), + modelAsString: xmse.modelAsString ? true : false + } + } + return undefined; } export function getKnownFormatType() { diff --git a/remodeler/remodeler.ts b/remodeler/remodeler.ts index a9b5844a20..9a22b114e0 100644 --- a/remodeler/remodeler.ts +++ b/remodeler/remodeler.ts @@ -151,6 +151,8 @@ export class Remodeler { newSchema.enum = [...original.enum]; } + newSchema.details.enum = Interpretations.getEnumDefinition(original); + // object properties // discriminator if (original.discriminator && original.discriminator.propertyName) { @@ -215,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: {}, } }) @@ -352,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] }); } @@ -386,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]); } } @@ -402,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]); } }