Initial version of tsp emitter (#1347)
* Init tsp * Add language node * Add query, path and global parameters * Add body request * Init schema * Add some workaround * initial support for enum * Handle unkown type * Fixed names, handle x-ms-mutability and fixed some issues * Fixed a few issues found in module generation * Fixed a few issues * Add tsphost * inital generator base on tsp * Fixed some minor issues * Add support for header response * Union support for enum * Skip deep clone for some operation properties * Fixed the issue for void input of request * Fixed a few issues * Fixed an issue related to anonymous object * remove redundant psNamer(state) * Add support for validation * upgrade tps to 0.57 * Fixed a lint error
This commit is contained in:
Родитель
cc5cec2eb7
Коммит
1bb7ece315
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -48,12 +48,12 @@ export class CmdletNamespace extends Namespace {
|
|||
operation.parameters = operation.parameters.filter(element => element.required === true);
|
||||
}
|
||||
const newClass = await new CmdletClass(this, operation, this.state.path('commands', 'operations', index)).init();
|
||||
|
||||
const refCopyPropertyNames = ['parameters', 'requests', 'responses', 'exceptions', 'requestMediaTypes'];
|
||||
if (operation.variant.includes('ViaJsonString')) {
|
||||
const name = 'JsonString';
|
||||
operation.details.csharp.name = `${operation.variant}Via${name}`;
|
||||
|
||||
operation.callGraph[operation.callGraph.length - 1] = clone(operation.callGraph[operation.callGraph.length - 1]);
|
||||
operation.callGraph[operation.callGraph.length - 1] = clone(operation.callGraph[operation.callGraph.length - 1], false, undefined, undefined, refCopyPropertyNames);
|
||||
operation.callGraph[operation.callGraph.length - 1].language.csharp!.name = `${(<any>operation.callGraph[operation.callGraph.length - 1]).language.csharp!.name}ViaJsonString`;
|
||||
}
|
||||
if (operation.variant.includes('ViaJsonFilePath')) {
|
||||
|
@ -67,7 +67,7 @@ export class CmdletNamespace extends Namespace {
|
|||
const jsonStringField = new Field('_jsonString', System.String);
|
||||
newClass.add(jsonStringField);
|
||||
|
||||
operation.callGraph[operation.callGraph.length - 1] = clone(operation.callGraph[operation.callGraph.length - 1]);
|
||||
operation.callGraph[operation.callGraph.length - 1] = clone(operation.callGraph[operation.callGraph.length - 1], false, undefined, undefined, refCopyPropertyNames);
|
||||
operation.callGraph[operation.callGraph.length - 1].language.csharp!.name = `${(<any>operation.callGraph[operation.callGraph.length - 1]).language.csharp!.name}ViaJsonString`;
|
||||
}
|
||||
this.addClass(newClass);
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export * from './utils/pwshModel';
|
||||
export * from './utils/tsp-generator';
|
|
@ -34,6 +34,7 @@ import {
|
|||
Schema as NewSchema,
|
||||
SchemaType,
|
||||
} from '@autorest/codemodel';
|
||||
import { TspHost } from '../utils/tsp-host';
|
||||
|
||||
export type Schema = T.SchemaT<
|
||||
LanguageDetails<SchemaDetails>,
|
||||
|
@ -186,17 +187,22 @@ export class Project extends codeDomProject {
|
|||
}
|
||||
|
||||
constructor(
|
||||
protected service: Host,
|
||||
protected service: Host | TspHost,
|
||||
objectInitializer?: DeepPartial<Project>
|
||||
) {
|
||||
super();
|
||||
this.apply(objectInitializer);
|
||||
}
|
||||
|
||||
public async init(): Promise<this> {
|
||||
public async init(state?: ModelState<PwshModel>): Promise<this> {
|
||||
await super.init();
|
||||
|
||||
this.state = await new State(this.service).init(this);
|
||||
|
||||
if (state) {
|
||||
this.state.model = state.model;
|
||||
}
|
||||
|
||||
this.schemaDefinitionResolver = new PSSchemaResolver();
|
||||
|
||||
this.projectNamespace = this.state.model.language.csharp?.namespace || '';
|
||||
|
@ -260,7 +266,7 @@ export class Project extends codeDomProject {
|
|||
// modelcmdlets are models that we will create cmdlets for.
|
||||
this.modelCmdlets = [];
|
||||
let directives: Array<any> = [];
|
||||
const allDirectives = await this.state.getValue('directive');
|
||||
const allDirectives = await this.state.service.getValue('directive');
|
||||
directives = values(<any>allDirectives).toArray();
|
||||
for (const directive of directives.filter((each) => each['model-cmdlet'])) {
|
||||
this.modelCmdlets = this.modelCmdlets.concat(
|
||||
|
|
|
@ -10,6 +10,7 @@ import { Project } from './project';
|
|||
import { DeepPartial } from '@azure-tools/codegen';
|
||||
import { PwshModel } from '../utils/PwshModel';
|
||||
import { ModelState } from '../utils/model-state';
|
||||
import { TspHost } from '../utils/tsp-host';
|
||||
|
||||
|
||||
export interface GeneratorSettings {
|
||||
|
@ -27,7 +28,7 @@ export interface GeneratorSettings {
|
|||
export class State extends ModelState<PwshModel> {
|
||||
project!: Project;
|
||||
|
||||
public constructor(service: Host, objectInitializer?: DeepPartial<State>) {
|
||||
public constructor(service: Host | TspHost, objectInitializer?: DeepPartial<State>) {
|
||||
super(service);
|
||||
this.apply(objectInitializer);
|
||||
}
|
||||
|
|
|
@ -12,11 +12,12 @@ import { Dictionary } from '@azure-tools/linq';
|
|||
import { DeepPartial } from '@azure-tools/codegen';
|
||||
import { PwshModel } from '../utils/PwshModel';
|
||||
import { ModelState } from '../utils/model-state';
|
||||
import { TspHost } from '../utils/tsp-host';
|
||||
|
||||
export class State extends ModelState<PwshModel> {
|
||||
project!: Project;
|
||||
|
||||
public constructor(service: Host, objectInitializer?: DeepPartial<State>) {
|
||||
public constructor(service: Host | TspHost, objectInitializer?: DeepPartial<State>) {
|
||||
super(service);
|
||||
this.apply(objectInitializer);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,9 @@ import { ServiceNamespace } from './operation/namespace';
|
|||
import { SupportNamespace } from './enums/namespace';
|
||||
import { DeepPartial } from '@azure-tools/codegen';
|
||||
import { PropertyFormat } from '../utils/schema';
|
||||
import { TspHost } from '../utils/tsp-host';
|
||||
import { ModelState } from '../utils/model-state';
|
||||
import { PwshModel } from '../utils/pwshModel';
|
||||
|
||||
export class Project extends codeDomProject {
|
||||
|
||||
|
@ -36,15 +39,18 @@ export class Project extends codeDomProject {
|
|||
supportJsonInput!: boolean;
|
||||
formats!: Dictionary<PropertyFormat>;
|
||||
|
||||
constructor(protected service: Host, objectInitializer?: DeepPartial<Project>) {
|
||||
constructor(protected service: Host | TspHost, objectInitializer?: DeepPartial<Project>) {
|
||||
super();
|
||||
this.apply(objectInitializer);
|
||||
}
|
||||
|
||||
public async init(): Promise<this> {
|
||||
public async init(state?: ModelState<PwshModel>): Promise<this> {
|
||||
await super.init();
|
||||
|
||||
this.state = await new State(this.service).init(this);
|
||||
if (state) {
|
||||
this.state.model = state.model;
|
||||
}
|
||||
|
||||
this.apifolder = await this.state.getValue('api-folder', '');
|
||||
this.runtimefolder = await this.state.getValue('runtime-folder', 'runtime');
|
||||
this.azure = await this.state.getValue('azure', false) || await this.state.getValue('azure-arm', false);
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
"devDependencies": {
|
||||
"@types/js-yaml": "3.12.1",
|
||||
"@types/mocha": "5.2.5",
|
||||
"@types/node": "12.7.2",
|
||||
"@types/node": "~20.14.0",
|
||||
"mocha": "10.2.0",
|
||||
"@testdeck/mocha": "0.3.3",
|
||||
"typescript": "~5.1.3",
|
||||
|
|
|
@ -22,7 +22,7 @@ const locationNames = new Set<string>([
|
|||
'location',
|
||||
]);
|
||||
|
||||
async function tweakModel(state: State): Promise<PwshModel> {
|
||||
export async function tweakModel(state: State): Promise<PwshModel> {
|
||||
const model = state.model;
|
||||
for (const operation of values(model.commands.operations)) {
|
||||
for (const parameter of values(operation.parameters)) {
|
||||
|
@ -46,5 +46,5 @@ async function tweakModel(state: State): Promise<PwshModel> {
|
|||
|
||||
export async function addCompleterV2(service: Host) {
|
||||
const state = await new ModelState<PwshModel>(service).init();
|
||||
await service.writeFile({filename: 'code-model-v4-add-azure-completers-v2.yaml', content: serialize(await tweakModel(state)), sourceMap: undefined, artifactType: 'code-model-v4'});
|
||||
await service.writeFile({ filename: 'code-model-v4-add-azure-completers-v2.yaml', content: serialize(await tweakModel(state)), sourceMap: undefined, artifactType: 'code-model-v4' });
|
||||
}
|
||||
|
|
|
@ -237,7 +237,7 @@ async function setOperationNames(state: State, resolver: SchemaDefinitionResolve
|
|||
}
|
||||
}
|
||||
|
||||
async function nameStuffRight(state: State): Promise<PwshModel> {
|
||||
export async function nameStuffRight(state: State): Promise<PwshModel> {
|
||||
const resolver = new SchemaDefinitionResolver();
|
||||
const model = state.model;
|
||||
|
||||
|
@ -271,6 +271,6 @@ export async function csnamerV2(service: Host) {
|
|||
//const session = await startSession<PwshModel>(service, {}, codeModelSchema);
|
||||
//const result = tweakModelV2(session);
|
||||
const state = await new ModelState<PwshModel>(service).init();
|
||||
await service.writeFile({ filename: 'code-model-v4-csnamer-v2.yaml', content: serialize(await nameStuffRight(state)), sourceMap: undefined, artifactType: 'code-model-v4'});
|
||||
await service.writeFile({ filename: 'code-model-v4-csnamer-v2.yaml', content: serialize(await nameStuffRight(state)), sourceMap: undefined, artifactType: 'code-model-v4' });
|
||||
}
|
||||
|
||||
|
|
|
@ -8,27 +8,30 @@ import { applyOverrides, copyResources, deserialize, serialize, } from '@azure-t
|
|||
import { join } from 'path';
|
||||
import { Model } from '../llcsharp/code-model';
|
||||
import { Project } from '../llcsharp/project';
|
||||
import { TspHost } from '../utils/tsp-host';
|
||||
import { ModelState } from '../utils/model-state';
|
||||
import { PwshModel } from '../utils/pwshModel';
|
||||
|
||||
const resources = `${__dirname}/../../resources`;
|
||||
|
||||
export async function llcsharpV2(service: Host) {
|
||||
export async function llcsharpV2(service: Host | TspHost, state?: ModelState<PwshModel>) {
|
||||
try {
|
||||
const project = await new Project(service).init();
|
||||
const project = await new Project(service).init(state);
|
||||
const transformOutput = async (input: string) => {
|
||||
return await project.state.resolveVariables(input);
|
||||
};
|
||||
|
||||
await project.writeFiles(async (fname, content) => service.writeFile({ filename: join(project.apifolder, fname), content: applyOverrides(content, project.overrides), sourceMap: undefined, artifactType: 'source-file-csharp'}));
|
||||
await project.writeFiles(async (fname, content) => project.state.writeFile(join(project.apifolder, fname), applyOverrides(content, project.overrides), undefined, 'source-file-csharp'));
|
||||
|
||||
// recursive copy resources
|
||||
await copyResources(join(resources, 'runtime', 'csharp', 'client'), async (fname, content) => service.writeFile({ filename: join(project.runtimefolder, fname), content: content, sourceMap: undefined, artifactType: 'source-file-csharp'}), project.overrides);
|
||||
await copyResources(join(resources, 'runtime', 'csharp', 'pipeline'), async (fname, content) => service.writeFile({ filename: join(project.runtimefolder, fname), content: content, sourceMap: undefined, artifactType: 'source-file-csharp'}), project.overrides, transformOutput);
|
||||
await copyResources(join(resources, 'runtime', 'csharp', 'client'), async (fname, content) => project.state.writeFile(join(project.runtimefolder, fname), content, undefined, 'source-file-csharp'), project.overrides);
|
||||
await copyResources(join(resources, 'runtime', 'csharp', 'pipeline'), async (fname, content) => project.state.writeFile(join(project.runtimefolder, fname), content, undefined, 'source-file-csharp'), project.overrides, transformOutput);
|
||||
// Note:
|
||||
// we are using the Carbon.Json library, but we don't *really* want to expose that as public members where we don't have to
|
||||
// and I don't want to make code changes in the source repository, so I can keep merging from upstream as simple as possible.
|
||||
// so, we're converting as much as possible to internal, and exposing only what must be exposed to make the code compile.
|
||||
|
||||
await copyResources(join(resources, 'runtime', 'csharp', 'json'), async (fname, content) => service.writeFile({ filename: join(project.runtimefolder, fname), content: content, sourceMap: undefined, artifactType: 'source-file-csharp'}), {
|
||||
await copyResources(join(resources, 'runtime', 'csharp', 'json'), async (fname, content) => project.state.writeFile(join(project.runtimefolder, fname), content, undefined, 'source-file-csharp'), {
|
||||
...project.overrides,
|
||||
'public': 'internal',
|
||||
'internal (.*) class JsonNumber': 'public $1 class JsonNumber',
|
||||
|
@ -65,11 +68,11 @@ export async function llcsharpV2(service: Host) {
|
|||
});
|
||||
|
||||
if (project.xmlSerialization) {
|
||||
await copyResources(join(resources, 'runtime', 'csharp', 'xml'), async (fname, content) => service.writeFile({ filename: join(project.runtimefolder, fname), content: content, sourceMap: undefined, artifactType: 'source-file-csharp'}), project.overrides);
|
||||
await copyResources(join(resources, 'runtime', 'csharp', 'xml'), async (fname, content) => project.state.writeFile(join(project.runtimefolder, fname), content, undefined, 'source-file-csharp'), project.overrides);
|
||||
}
|
||||
|
||||
if (project.azure) {
|
||||
await copyResources(join(resources, 'runtime', 'csharp.azure'), async (fname, content) => service.writeFile({ filename: join(project.runtimefolder, fname), content: content, sourceMap: undefined, artifactType: 'source-file-csharp'}), project.overrides);
|
||||
await copyResources(join(resources, 'runtime', 'csharp.azure'), async (fname, content) => project.state.writeFile(join(project.runtimefolder, fname), content, undefined, 'source-file-csharp'), project.overrides);
|
||||
}
|
||||
|
||||
} catch (E) {
|
||||
|
|
|
@ -336,6 +336,14 @@ function isWhereEnumDirective(it: any): it is WhereEnumDirective {
|
|||
return false;
|
||||
}
|
||||
|
||||
export async function tweakModelForTsp(state: State): Promise<PwshModel> {
|
||||
const allDirectives = await state.service.getValue<any>('directive');
|
||||
directives = values(allDirectives)
|
||||
.where(directive => isWhereCommandDirective(directive) || isWhereModelDirective(directive) || isWhereEnumDirective(directive) || isRemoveCommandDirective(directive))
|
||||
.toArray();
|
||||
return await tweakModel(state);
|
||||
}
|
||||
|
||||
async function tweakModel(state: State): Promise<PwshModel> {
|
||||
|
||||
const isAzure = await state.getValue('azure', false) || await state.getValue('azure-arm', false);
|
||||
|
|
|
@ -409,7 +409,7 @@ function createVirtualParameters(operation: CommandOperation) {
|
|||
operation.details.default.virtualParameters = virtualParameters;
|
||||
}
|
||||
|
||||
async function createVirtuals(state: State): Promise<PwshModel> {
|
||||
export async function createVirtuals(state: State): Promise<PwshModel> {
|
||||
/*
|
||||
A model class should provide inlined properties for anything in a property called properties
|
||||
|
||||
|
|
|
@ -14,7 +14,13 @@ import { AutorestExtensionHost as Host } from '@autorest/extension-base';
|
|||
|
||||
type State = ModelState<PwshModel>;
|
||||
|
||||
let directives: Array<any> = [];
|
||||
export let directives: Array<any> = [];
|
||||
|
||||
export async function tweakModelForTsp(state: State): Promise<PwshModel> {
|
||||
const allDirectives = await state.service.getValue<any>('directive');
|
||||
directives = values(allDirectives).toArray();
|
||||
return await tweakModel(state);
|
||||
}
|
||||
|
||||
async function tweakModel(state: State): Promise<PwshModel> {
|
||||
const model = state.model;
|
||||
|
@ -208,3 +214,4 @@ export async function tweakM4ModelPlugin(service: Host) {
|
|||
const state = await new ModelState<PwshModel>(service).init();
|
||||
service.writeFile({ filename: 'code-model-v4-tweakm4codemodel.yaml', content: serialize(await tweakModel(state)), sourceMap: undefined, artifactType: 'code-model-v4' });
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ type State = ModelState<PwshModel>;
|
|||
|
||||
const xmsPageable = 'x-ms-pageable';
|
||||
|
||||
async function tweakModel(state: State): Promise<PwshModel> {
|
||||
export async function tweakModel(state: State): Promise<PwshModel> {
|
||||
const model = state.model;
|
||||
|
||||
// service.message{ Channel: Channel.Debug, Text: "THIS IS THE AZURE TWEAKER" });
|
||||
|
@ -198,5 +198,5 @@ function getSchema(response: Response): Schema {
|
|||
|
||||
export async function tweakModelAzurePluginV2(service: Host) {
|
||||
const state = await new ModelState<PwshModel>(service).init();
|
||||
await service.writeFile({ filename: 'code-model-v4-tweakcodemodelazure-v2.yaml', content: serialize(await tweakModel(state)), sourceMap: undefined, artifactType: 'code-model-v4'});
|
||||
await service.writeFile({ filename: 'code-model-v4-tweakcodemodelazure-v2.yaml', content: serialize(await tweakModel(state)), sourceMap: undefined, artifactType: 'code-model-v4' });
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ function dropDuplicatePropertiesInChildSchemas(schema: ObjectSchema, state: Stat
|
|||
return success;
|
||||
}
|
||||
|
||||
async function tweakModelV2(state: State): Promise<PwshModel> {
|
||||
export async function tweakModelV2(state: State): Promise<PwshModel> {
|
||||
const title = pascalCase(fixLeadingNumber(deconstruct(await state.getValue('title', state.model.info.title))));
|
||||
state.setValue('title', title);
|
||||
|
||||
|
@ -645,6 +645,6 @@ export async function tweakModelPlugin(service: Host) {
|
|||
//const session = await startSession<PwshModel>(service, {}, codeModelSchema);
|
||||
const state = await new ModelState<PwshModel>(service).init();
|
||||
//const result = tweakModelV2(session);
|
||||
await service.writeFile({ filename: 'code-model-v4-tweakcodemodel-v2.yaml', content: serialize(await tweakModelV2(state)), sourceMap: undefined, artifactType: 'code-model-v4'});
|
||||
await service.writeFile({ filename: 'code-model-v4-tweakcodemodel-v2.yaml', content: serialize(await tweakModelV2(state)), sourceMap: undefined, artifactType: 'code-model-v4' });
|
||||
//return processCodeModel(tweakModelV2, service, 'tweakcodemodel-v2');
|
||||
}
|
||||
|
|
|
@ -24,6 +24,10 @@ import { generateGitIgnore } from '../generators/gitignore';
|
|||
import { generateGitAttributes } from '../generators/gitattributes';
|
||||
import { generateReadme } from '../generators/readme';
|
||||
import { generateScriptCmdlets } from '../generators/script-cmdlet';
|
||||
import { TspHost } from '../utils/tsp-host';
|
||||
import { State } from '../internal/state';
|
||||
import { ModelState } from '../utils/model-state';
|
||||
import { PwshModel } from '../utils/pwshModel';
|
||||
|
||||
const sourceFileCSharp = 'source-file-csharp';
|
||||
const resources = `${__dirname}/../../resources`;
|
||||
|
@ -99,11 +103,11 @@ async function copyRequiredFiles(project: Project) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function powershellV2(service: Host) {
|
||||
const debug = (await service.getValue('debug')) || false;
|
||||
export async function powershellV2(service: Host | TspHost, state?: ModelState<PwshModel>) {
|
||||
let debug = false;
|
||||
|
||||
try {
|
||||
const project = await new Project(service).init();
|
||||
const project = await new Project(service).init(state);
|
||||
|
||||
await project.writeFiles(async (filename, content) =>
|
||||
project.state.writeFile(
|
||||
|
@ -113,15 +117,15 @@ export async function powershellV2(service: Host) {
|
|||
sourceFileCSharp
|
||||
)
|
||||
);
|
||||
|
||||
await service.protectFiles(project.psd1);
|
||||
await service.protectFiles(project.readme);
|
||||
await service.protectFiles(project.customFolder);
|
||||
await service.protectFiles(project.testFolder);
|
||||
await service.protectFiles(project.docsFolder);
|
||||
await service.protectFiles(project.examplesFolder);
|
||||
await service.protectFiles(project.resourcesFolder);
|
||||
await service.protectFiles(project.uxFolder);
|
||||
debug = (await project.state.service.getValue('debug')) || false;
|
||||
await project.state.protectFiles(project.psd1);
|
||||
await project.state.protectFiles(project.readme);
|
||||
await project.state.protectFiles(project.customFolder);
|
||||
await project.state.protectFiles(project.testFolder);
|
||||
await project.state.protectFiles(project.docsFolder);
|
||||
await project.state.protectFiles(project.examplesFolder);
|
||||
await project.state.protectFiles(project.resourcesFolder);
|
||||
await project.state.protectFiles(project.uxFolder);
|
||||
|
||||
// wait for all the generation to be done
|
||||
await copyRequiredFiles(project);
|
||||
|
|
|
@ -47,7 +47,7 @@ export function getDeduplicatedNoun(subjectPrefix: string, subject: string): { s
|
|||
return { subjectPrefix: pascalCase(finalPrefix), subject: pascalCase(reversedFinalSubject.reverse()) };
|
||||
}
|
||||
|
||||
async function tweakModel(state: State): Promise<PwshModel> {
|
||||
export async function tweakModel(state: State): Promise<PwshModel> {
|
||||
// get the value
|
||||
const isAzure = await state.getValue('azure', false);
|
||||
// without setting snitize-names, isAzure is applied
|
||||
|
@ -209,5 +209,5 @@ export async function namerV2(service: Host) {
|
|||
//const session = await startSession<PwshModel>(service, {}, codeModelSchema);
|
||||
//const result = tweakModelV2(session);
|
||||
const state = await new ModelState<PwshModel>(service).init();
|
||||
await service.writeFile({ filename: 'code-model-v4-psnamer-v2.yaml', content: serialize(await tweakModel(state)), sourceMap: undefined, artifactType: 'code-model-v4'});
|
||||
await service.writeFile({ filename: 'code-model-v4-psnamer-v2.yaml', content: serialize(await tweakModel(state)), sourceMap: undefined, artifactType: 'code-model-v4' });
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
import { Channel, AutorestExtensionHost as Host, JsonPointerSegments as JsonPath, Mapping, RawSourceMap, Message } from '@autorest/extension-base';
|
||||
import { safeEval, deserialize, Initializer, DeepPartial } from '@azure-tools/codegen';
|
||||
import { Dictionary } from '@azure-tools/linq';
|
||||
import { TspHost, TspHostImpl } from './tsp-host';
|
||||
|
||||
export class ModelState<T extends Dictionary<any>> extends Initializer {
|
||||
public model!: T;
|
||||
|
@ -15,12 +16,17 @@ export class ModelState<T extends Dictionary<any>> extends Initializer {
|
|||
private _debug = false;
|
||||
private _verbose = false;
|
||||
|
||||
public constructor(protected service: Host, objectInitializer?: DeepPartial<ModelState<T>>) {
|
||||
public constructor(public service: TspHost | Host, objectInitializer?: DeepPartial<ModelState<T>>) {
|
||||
super();
|
||||
this.apply(objectInitializer);
|
||||
}
|
||||
|
||||
async init(project?: any) {
|
||||
if (this.service instanceof TspHostImpl) {
|
||||
// skip init for tsp
|
||||
this.initContext(project);
|
||||
return this;
|
||||
}
|
||||
const m = await ModelState.getModel<T>(this.service);
|
||||
this.model = m.model;
|
||||
this.documentName = m.filename;
|
||||
|
@ -88,8 +94,8 @@ export class ModelState<T extends Dictionary<any>> extends Initializer {
|
|||
async protectFiles(path: string): Promise<void> {
|
||||
return this.service.protectFiles(path);
|
||||
}
|
||||
writeFile(filename: string, content: string, sourceMap?: Array<Mapping> | RawSourceMap | undefined, artifactType?: string | undefined): void {
|
||||
return this.service.writeFile({ filename: filename, content: content, sourceMap: sourceMap, artifactType: artifactType});
|
||||
writeFile(filename: string, content: string, sourceMap?: undefined, artifactType?: string | undefined): void {
|
||||
return this.service.writeFile({ filename: filename, content: content, sourceMap: sourceMap, artifactType: artifactType });
|
||||
}
|
||||
|
||||
message(message: Message): void {
|
||||
|
@ -110,7 +116,7 @@ export class ModelState<T extends Dictionary<any>> extends Initializer {
|
|||
}
|
||||
protected errorCount = 0;
|
||||
|
||||
protected static async getModel<T>(service: Host) {
|
||||
protected static async getModel<T>(service: Host | TspHost) {
|
||||
const files = await service.listInputs();
|
||||
const filename = files[0];
|
||||
if (files.length === 0) {
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import { PwshModel } from '../utils/PwshModel';
|
||||
import { createVirtuals } from '../plugins/plugin-create-inline-properties';
|
||||
import { tweakModelV2 } from '../plugins/plugin-tweak-model';
|
||||
import { tweakModel as tweakModelAzure } from '../plugins/plugin-tweak-model-azure-v2';
|
||||
import { Inferrer } from '../plugins/create-commands-v2';
|
||||
import { nameStuffRight } from '../plugins/cs-namer-v2';
|
||||
import { tweakModel as psNamer } from '../plugins/ps-namer-v2';
|
||||
import { llcsharpV2 } from '../plugins/llcsharp-v2';
|
||||
import { powershellV2 } from '../plugins/powershell-v2';
|
||||
import { tweakModel } from '../plugins/add-azure-completers-v2';
|
||||
import { tweakModelForTsp as modifier } from '../plugins/modifiers-v2';
|
||||
import { tweakModelForTsp as tweakM4Model } from '../plugins/plugin-tweak-m4-model';
|
||||
import { ModelState } from './model-state';
|
||||
import { TspHostImpl } from './tsp-host';
|
||||
import { stat } from 'fs';
|
||||
import { serialize } from '@azure-tools/codegen';
|
||||
|
||||
export async function generatePwshModule(pwshModel: PwshModel, emitterOptions: any) {
|
||||
const tspService = new TspHostImpl(emitterOptions);
|
||||
const state = await new ModelState<PwshModel>(tspService);
|
||||
state.model = pwshModel;
|
||||
await tweakM4Model(state);
|
||||
await tweakModelV2(state);
|
||||
await tweakModelAzure(state);
|
||||
await (await new Inferrer(state).init()).createCommands();
|
||||
await createVirtuals(state);
|
||||
await nameStuffRight(state);
|
||||
await psNamer(state);
|
||||
await modifier(state);
|
||||
await llcsharpV2(tspService, state);
|
||||
await powershellV2(tspService, state);
|
||||
return;
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
import { Message } from '@autorest/extension-base';
|
||||
import { writeFileSync, existsSync, mkdirSync } from 'fs';
|
||||
import { dirname } from 'path';
|
||||
export interface TspWriteFileOptions {
|
||||
/**
|
||||
* @param filename Name of the file.
|
||||
*/
|
||||
filename: string;
|
||||
/**
|
||||
* @param content Content of the file.
|
||||
*/
|
||||
content: string;
|
||||
/**
|
||||
* @param sourceMap Source map that can be used to trace back source position in case of error.
|
||||
*/
|
||||
sourceMap?: string;
|
||||
/**
|
||||
* @param artifactType Artifact type
|
||||
*/
|
||||
artifactType?: string;
|
||||
}
|
||||
|
||||
export interface TspHost {
|
||||
protectFiles(path: string): Promise<void>;
|
||||
readFile(filename: string): Promise<string>;
|
||||
getValue<T>(key: string): Promise<T | undefined>;
|
||||
listInputs(artifactType?: string): Promise<Array<string>>;
|
||||
writeFile({ filename, content, sourceMap, artifactType }: TspWriteFileOptions): void;
|
||||
message(message: Message): void;
|
||||
|
||||
UpdateConfigurationFile(filename: string, content: string): void;
|
||||
|
||||
GetConfigurationFile(filename: string): Promise<string>;
|
||||
|
||||
// WriteFile(filename: string, content: string): void;
|
||||
|
||||
// GetValue(key: string): any;
|
||||
}
|
||||
|
||||
export class TspHostImpl implements TspHost {
|
||||
configurations: Record<string, any>;
|
||||
constructor(configurations: Record<string, any>) {
|
||||
this.configurations = configurations;
|
||||
}
|
||||
protectFiles(path: string): Promise<void> {
|
||||
// ToDo by xiaogang, skip this for now
|
||||
return Promise.resolve();
|
||||
}
|
||||
readFile(filename: string): Promise<string> {
|
||||
// ToDo by xiaogang, skip this for now
|
||||
return Promise.resolve('');
|
||||
}
|
||||
getValue<T>(key: string): Promise<T | undefined> {
|
||||
return this.configurations[key];
|
||||
}
|
||||
listInputs(artifactType?: string): Promise<Array<string>> {
|
||||
// Shall not be called, so throw an exception
|
||||
throw new Error('Method listInputs not implemented');
|
||||
}
|
||||
writeFile({ filename, content, sourceMap, artifactType }: TspWriteFileOptions): void {
|
||||
const directoryPath = dirname(filename);
|
||||
if (!existsSync(directoryPath)) {
|
||||
mkdirSync(directoryPath, { recursive: true });
|
||||
}
|
||||
if (artifactType === 'binary-file') {
|
||||
writeFileSync(filename, Buffer.from(content, 'base64'));
|
||||
} else {
|
||||
writeFileSync(filename, content);
|
||||
}
|
||||
}
|
||||
UpdateConfigurationFile(filename: string, content: string): void {
|
||||
// Shall not be called
|
||||
throw new Error('Method UpdateConfigurationFile not implemented');
|
||||
}
|
||||
GetConfigurationFile(filename: string): Promise<string> {
|
||||
throw new Error('Method GetConfigurationFile not implemented');
|
||||
}
|
||||
// WriteFile(filename: string, content: string): void {
|
||||
// throw new Error('Method not implemented');
|
||||
// }
|
||||
// GetValue(key: string): any {
|
||||
// throw new Error('Method not implemented');
|
||||
// }
|
||||
message(message: Message): void {
|
||||
// ToDo by xiaogang, skip this for now
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
root: true
|
||||
env:
|
||||
es2021: true
|
||||
node: true
|
||||
extends:
|
||||
- eslint:recommended
|
||||
- plugin:@typescript-eslint/recommended
|
||||
parser: "@typescript-eslint/parser"
|
||||
parserOptions:
|
||||
ecmaVersion: latest
|
||||
sourceType: module
|
||||
plugins:
|
||||
- "@typescript-eslint"
|
||||
rules: {}
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"name": "@azure-tools/typespec-powershell",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"main": "dist/src/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/src/index.d.ts",
|
||||
"default": "./dist/src/index.js"
|
||||
},
|
||||
"./testing": {
|
||||
"types": "./dist/src/testing/index.d.ts",
|
||||
"default": "./dist/src/testing/index.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@autorest/codemodel": "~4.19.3",
|
||||
"@autorest/extension-base": "~3.5.2",
|
||||
"@azure-tools/async-io": "~3.0.0",
|
||||
"@azure-tools/codegen": "~2.5.276",
|
||||
"@azure-tools/codegen-csharp": "~3.0.0",
|
||||
"@azure-tools/codemodel-v3": "~3.1.0",
|
||||
"@azure-tools/linq": "~3.1.0",
|
||||
"@azure-tools/tasks": "~3.0.0",
|
||||
"ejs": "~3.1.8",
|
||||
"js-yaml": "3.13.1",
|
||||
"source-map-support": "0.5.13",
|
||||
"xmlbuilder": "10.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typespec/compiler": ">=0.51.0 <1.0.0",
|
||||
"@testdeck/mocha": "0.3.3",
|
||||
"@types/js-yaml": "3.12.1",
|
||||
"@types/mocha": "5.2.5",
|
||||
"@types/node": "~20.14.0",
|
||||
"@types/xmlbuilder": "0.0.34",
|
||||
"@typescript-eslint/eslint-plugin": "~5.62.0",
|
||||
"@typescript-eslint/parser": "~5.62.0",
|
||||
"eslint": "~8.12.0",
|
||||
"mocha": "10.2.0",
|
||||
"prettier": "^3.0.3",
|
||||
"typescript": "~5.1.3",
|
||||
"vitest": "1.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@azure-tools/typespec-azure-core": ">=0.37.2 <1.0.0",
|
||||
"@azure-tools/typespec-client-generator-core": ">=0.37.0 <1.0.0",
|
||||
"@typespec/compiler": ">=0.51.0 <1.0.0",
|
||||
"@typespec/http": ">=0.51.0 <1.0.0",
|
||||
"@typespec/rest": ">=0.51.0 <1.0.0",
|
||||
"@typespec/versioning": ">=0.51.0 <1.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc --watch",
|
||||
"test": "node --test ./dist/test/",
|
||||
"lint": "eslint src/ test/ --report-unused-disable-directives --max-warnings=0",
|
||||
"lint:fix": "eslint . --report-unused-disable-directives --fix",
|
||||
"format": "prettier . --write",
|
||||
"format:check": "prettier --check ."
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
trailingComma: "all"
|
||||
printWidth: 120
|
||||
quoteProps: "consistent"
|
||||
endOfLine: lf
|
||||
arrowParens: always
|
||||
plugins:
|
||||
- "./node_modules/@typespec/prettier-plugin-typespec/dist/index.js"
|
||||
overrides: [{ "files": "*.tsp", "options": { "parser": "typespec" } }]
|
|
@ -0,0 +1,370 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import { SdkClient, SdkContext, listOperationsInOperationGroup, listOperationGroups } from "@azure-tools/typespec-client-generator-core";
|
||||
import { HttpOperation, HttpOperationParameter, HttpOperationRequestBody, getHttpOperation } from "@typespec/http";
|
||||
import { getDoc, getService, ignoreDiagnostics, Program, Model, Type } from "@typespec/compiler";
|
||||
import { getServers } from "@typespec/http";
|
||||
import { join } from "path";
|
||||
import { PwshModel } from "@autorest/powershell";
|
||||
// import { CodeModel as PwshModel } from "@autorest/codemodel";
|
||||
import { constantSchemaForApiVersion, getDefaultService, getSchemaForType, schemaCache, stringSchemaForEnum, numberSchemaForEnum, getSchemaForApiVersion, getEnrichedDefaultApiVersion } from "../utils/modelUtils.js";
|
||||
import { Info, Language, Schemas, AllSchemaTypes, SchemaType, ArraySchema, StringSchema, Languages } from "@autorest/codemodel";
|
||||
import { deconstruct, pascalCase, serialize } from "@azure-tools/codegen";
|
||||
import { PSOptions } from "../types/interfaces.js";
|
||||
import { Request, ImplementationLocation, OperationGroup, Operation, Parameter, Schema, Protocol, Response, HttpHeader } from "@autorest/codemodel";
|
||||
import { stat } from "fs";
|
||||
import { extractPagedMetadataNested } from "../utils/operationUtil.js";
|
||||
import { parseNextLinkName } from "../utils/operationUtil.js";
|
||||
import { getLroMetadata } from "@azure-tools/typespec-azure-core";
|
||||
|
||||
const GlobalParameter = "global-parameter";
|
||||
|
||||
export async function transformPwshModel(
|
||||
client: SdkClient,
|
||||
psContext: SdkContext,
|
||||
emitterOptions: PSOptions
|
||||
): Promise<PwshModel> {
|
||||
const model = new PwshModel(client.name);
|
||||
model.info = getServiceInfo(psContext.program);
|
||||
model.language.default = getLanguageDefault(psContext.program, emitterOptions);
|
||||
model.operationGroups = getOperationGroups(psContext.program, client, psContext, model);
|
||||
model.schemas = getSchemas(psContext.program, client, psContext, model);
|
||||
return model;
|
||||
}
|
||||
|
||||
function getSchemas(program: Program, client: SdkClient, psContext: SdkContext, model: PwshModel): Schemas {
|
||||
const schemas = new Schemas();
|
||||
for (const schema of schemaCache.values()) {
|
||||
if (schema.type === SchemaType.Any) {
|
||||
// set name and description for any schema
|
||||
schema.language.default.name = "any";
|
||||
schema.language.default.description = "Anything";
|
||||
schemas["any"] = schemas["any"] || [];
|
||||
schemas["any"].push(schema);
|
||||
} else {
|
||||
if (schema.type === SchemaType.Array && (<any>schema).delayType) {
|
||||
(<ArraySchema>schema).elementType = getSchemaForType(psContext, (<any>schema).delayType as Type);
|
||||
(<any>schema).delayType = undefined;
|
||||
}
|
||||
schemas.add(schema);
|
||||
}
|
||||
}
|
||||
|
||||
if (stringSchemaForEnum) {
|
||||
schemas.add(stringSchemaForEnum);
|
||||
}
|
||||
if (numberSchemaForEnum) {
|
||||
schemas.add(numberSchemaForEnum);
|
||||
}
|
||||
if (constantSchemaForApiVersion) {
|
||||
schemas.add(constantSchemaForApiVersion);
|
||||
}
|
||||
return schemas;
|
||||
}
|
||||
function getOperationGroups(program: Program, client: SdkClient, psContext: SdkContext, model: PwshModel): OperationGroup[] {
|
||||
const operationGroups: OperationGroup[] = [];
|
||||
// list all the operations in the client
|
||||
const clientOperations = listOperationsInOperationGroup(psContext, client);
|
||||
const newGroup = new OperationGroup("");
|
||||
if (clientOperations.length > 0) {
|
||||
newGroup.language.default.name = newGroup.$key = "";
|
||||
operationGroups.push(newGroup);
|
||||
}
|
||||
for (const clientOperation of clientOperations) {
|
||||
const op = ignoreDiagnostics(getHttpOperation(program, clientOperation));
|
||||
if (op.overloads && op.overloads.length > 0) {
|
||||
continue
|
||||
}
|
||||
addOperation(psContext, op, newGroup, model);
|
||||
// const operationGroup = new OperationGroup(operation.name);
|
||||
// operationGroup.language.default.name = operation.name;
|
||||
// operationGroup.language.default.description = operation.description;
|
||||
// operationGroup.operations.push(operation);
|
||||
// operationGroups.push(operationGroup);
|
||||
}
|
||||
const opGroups = listOperationGroups(psContext, client, true);
|
||||
for (const operationGroup of opGroups) {
|
||||
const newGroup = new OperationGroup("");
|
||||
newGroup.language.default.name = newGroup.$key = operationGroup.type.name;
|
||||
operationGroups.push(newGroup);
|
||||
const operations = listOperationsInOperationGroup(
|
||||
psContext,
|
||||
operationGroup
|
||||
);
|
||||
for (const operation of operations) {
|
||||
const op = ignoreDiagnostics(getHttpOperation(program, operation));
|
||||
// ignore overload base operation
|
||||
if (op.overloads && op.overloads?.length > 0) {
|
||||
continue;
|
||||
}
|
||||
addOperation(psContext, op, newGroup, model);
|
||||
}
|
||||
}
|
||||
return operationGroups;
|
||||
}
|
||||
|
||||
function addOperation(psContext: SdkContext, op: HttpOperation, operationGroup: OperationGroup, model: PwshModel) {
|
||||
const newOperation = new Operation(pascalCase(op.operation.name), getDoc(psContext.program, op.operation) ?? "");
|
||||
newOperation.operationId = operationGroup.$key + "_" + pascalCase(op.operation.name);
|
||||
// Add Api versions
|
||||
newOperation.apiVersions = newOperation.apiVersions || [];
|
||||
newOperation.apiVersions.push({ version: getEnrichedDefaultApiVersion(psContext.program, psContext) || "" });
|
||||
// Add query and path parameters
|
||||
const parameters = op.parameters.parameters.filter(p => p.type === "path" || p.type === "query");
|
||||
for (const parameter of parameters) {
|
||||
const newParameter = createParameter(psContext, parameter, model);
|
||||
newOperation.parameters = newOperation.parameters || [];
|
||||
newOperation.parameters.push(newParameter);
|
||||
}
|
||||
// Add request
|
||||
newOperation.requests = newOperation.requests || [];
|
||||
const newRequest = new Request();
|
||||
newOperation.requests.push(newRequest);
|
||||
const headerParameters = op.parameters.parameters.filter(p => p.type === "header");
|
||||
for (const parameter of headerParameters) {
|
||||
const newParameter = createParameter(psContext, parameter, model);
|
||||
newRequest.parameters = newRequest.parameters || [];
|
||||
newOperation.requests[0].parameters?.push(newParameter);
|
||||
}
|
||||
// Add host parameter
|
||||
const hostParameter = createHostParameter(psContext, model);
|
||||
newOperation.parameters = newOperation.parameters || [];
|
||||
newOperation.parameters.push(hostParameter);
|
||||
// Add request body if it exists
|
||||
if (op.parameters.body && op.parameters.body.bodyKind === "single" && !(op.parameters.body.type.kind === "Intrinsic" && op.parameters.body.type.name === "void")) {
|
||||
const newParameter = createBodyParameter(psContext, op.parameters.body, model);
|
||||
newRequest.parameters = newRequest.parameters || [];
|
||||
newOperation.requests[0].parameters?.push(newParameter);
|
||||
}
|
||||
const httpProtocol = new Protocol();
|
||||
httpProtocol.method = op.verb;
|
||||
httpProtocol.path = op.path;
|
||||
// hard code the media type to json for the time being by xiaogang.
|
||||
httpProtocol.knownMediaType = "json";
|
||||
httpProtocol.mediaTypes = ["application/json"];
|
||||
httpProtocol.uri = "{$host}";
|
||||
newOperation.requests[0].protocol.http = httpProtocol;
|
||||
|
||||
// Add responses include exceptions
|
||||
addResponses(psContext, op, newOperation, model);
|
||||
// Add extensions
|
||||
addExtensions(psContext, op, newOperation, model);
|
||||
operationGroup.addOperation(newOperation);
|
||||
}
|
||||
|
||||
function addExtensions(psContext: SdkContext, op: HttpOperation, newOperation: Operation, model: PwshModel) {
|
||||
// Add extensions for pageable
|
||||
const paged = extractPagedMetadataNested(psContext.program, op.responses[0].type as Model);
|
||||
if (paged) {
|
||||
newOperation.extensions = newOperation.extensions || {};
|
||||
//ToDo: add value if it is specified by xiaogang
|
||||
newOperation.extensions['x-ms-pageable'] = newOperation.extensions['x-ms-pageable'] || {};
|
||||
newOperation.extensions['x-ms-pageable']['nextLinkName'] = parseNextLinkName(paged) ?? "nextLink";
|
||||
newOperation.language.default.paging = newOperation.language.default.paging || {};
|
||||
newOperation.language.default.paging.nextLinkName = parseNextLinkName(paged) ?? "nextLink";
|
||||
}
|
||||
// Add extensions for long running operation
|
||||
const lro = getLroMetadata(psContext.program, op.operation);
|
||||
if (lro) {
|
||||
newOperation.extensions = newOperation.extensions || {};
|
||||
newOperation.extensions['x-ms-long-running-operation'] = true;
|
||||
newOperation.extensions['x-ms-long-running-operation-options'] = newOperation.extensions['x-ms-long-running-operation-options'] || {};
|
||||
newOperation.extensions['x-ms-long-running-operation-options']['final-state-via'] = lro.finalStateVia;
|
||||
}
|
||||
}
|
||||
|
||||
function addResponses(psContext: SdkContext, op: HttpOperation, newOperation: Operation, model: PwshModel) {
|
||||
const responses = op.responses;
|
||||
newOperation.responses = newOperation.responses || [];
|
||||
newOperation.exceptions = newOperation.exceptions || [];
|
||||
if (responses) {
|
||||
for (const response of responses) {
|
||||
const newResponse = new Response();
|
||||
// newOperation.responses[response.statusCode] || newOperation.responses.default;
|
||||
// if (!newResponse) {
|
||||
// newOperation.responses[response.statusCode] = newResponse;
|
||||
// }
|
||||
newResponse.language.default.name = '';
|
||||
newResponse.language.default.description = response.description || "";
|
||||
const statusCode = response.statusCode;
|
||||
// Add schema for response body
|
||||
if (response.responses[0].body) {
|
||||
const schema = getSchemaForType(psContext, response.responses[0].body.type);
|
||||
(<any>newResponse).schema = schema;
|
||||
}
|
||||
// Add headers
|
||||
newResponse.protocol.http = newResponse.protocol.http ?? new Protocol();
|
||||
if (response.responses[0].headers) {
|
||||
for (const key in response.responses[0].headers) {
|
||||
newResponse.protocol.http.headers = newResponse.protocol.http.headers || [];
|
||||
const header = response.responses[0].headers[key];
|
||||
const headerSchema = getSchemaForType(psContext, header.type);
|
||||
const headerResponse = new HttpHeader(key, headerSchema);
|
||||
headerResponse.language = new Languages();
|
||||
headerResponse.language.default = new Language();
|
||||
headerResponse.language.default.description = getDoc(psContext.program, header) || "";
|
||||
headerResponse.language.default.name = pascalCase(deconstruct(key));
|
||||
newResponse.protocol.http.headers.push(headerResponse);
|
||||
}
|
||||
}
|
||||
newResponse.protocol.http.statusCodes = statusCode === "*" ? ["default"] : [statusCode];
|
||||
newResponse.protocol.http.knownMediaType = "json";
|
||||
newResponse.protocol.http.mediaTypes = ["application/json"];
|
||||
if (statusCode.startsWith("2")) {
|
||||
newOperation.responses.push(newResponse);
|
||||
} else {
|
||||
newOperation.exceptions.push(newResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createBodyParameter(psContext: SdkContext, parameter: HttpOperationRequestBody, model: PwshModel): Parameter {
|
||||
const paramSchema = parameter.parameter?.sourceProperty
|
||||
? getSchemaForType(psContext, parameter.parameter?.sourceProperty?.type)
|
||||
: getSchemaForType(psContext, parameter.type)
|
||||
const newParameter = new Parameter(parameter.parameter?.name || "", parameter.parameter ? getDoc(psContext.program, parameter.parameter) || "" : "", paramSchema);
|
||||
newParameter.protocol.http = newParameter.protocol.http ?? new Protocol();
|
||||
newParameter.protocol.http.in = "body";
|
||||
// ToDo, we need to support x-ms-client is specified.
|
||||
newParameter.implementation = ImplementationLocation.Method;
|
||||
newParameter.required = !parameter.parameter?.optional;
|
||||
return newParameter;
|
||||
}
|
||||
|
||||
function createHostParameter(psContext: SdkContext, model: PwshModel): Parameter {
|
||||
const matchParameters = (model.globalParameters || []).filter(p => p.language.default.serializedName === '$host');
|
||||
if (matchParameters.length > 0) {
|
||||
return matchParameters[0];
|
||||
} else {
|
||||
const newParameter = new Parameter('$host', "server parameter", new StringSchema("", "")); //getSchemaForType(psContext, "string")
|
||||
newParameter.language.default.serializedName = '$host';
|
||||
newParameter.clientDefaultValue = "https://management.azure.com";
|
||||
newParameter.protocol.http = newParameter.protocol.http ?? new Protocol();
|
||||
newParameter.protocol.http.in = "uri";
|
||||
newParameter.implementation = ImplementationLocation.Client;
|
||||
newParameter.required = true;
|
||||
newParameter.extensions = newParameter.extensions || {};
|
||||
newParameter.extensions["x-ms-skip-url-encoding"] = true;
|
||||
model.globalParameters = model.globalParameters || [];
|
||||
model.globalParameters.push(newParameter);
|
||||
return newParameter;
|
||||
}
|
||||
}
|
||||
function createParameter(psContext: SdkContext, parameter: HttpOperationParameter, model: PwshModel): Parameter {
|
||||
if (parameter.type === "query" && parameter.name === "api-version"
|
||||
|| parameter.type === "path" && parameter.name === "subscriptionId") {
|
||||
const matchParameters = (model.globalParameters || []).filter(p => p.language.default.serializedName === parameter.name);
|
||||
if (matchParameters.length > 0) {
|
||||
return matchParameters[0];
|
||||
} else {
|
||||
const paramSchema = parameter.name === "api-version" ? getSchemaForApiVersion(psContext, parameter.param.type) : (parameter.param.sourceProperty ? getSchemaForType(psContext, parameter.param.sourceProperty) : getSchemaForType(psContext, parameter.param));
|
||||
const newParameter = new Parameter(pascalCase(deconstruct(parameter.name)), getDoc(psContext.program, parameter.param) || "", paramSchema);
|
||||
if (newParameter.language.default.name === "ApiVersion") {
|
||||
//to align with modelerfour
|
||||
newParameter.language.default.name = "apiVersion";
|
||||
}
|
||||
newParameter.language.default.serializedName = parameter.name;
|
||||
newParameter.protocol.http = newParameter.protocol.http ?? new Protocol();
|
||||
newParameter.protocol.http.in = parameter.type;
|
||||
newParameter.implementation = ImplementationLocation.Client;
|
||||
newParameter.required = !parameter.param.optional;
|
||||
model.globalParameters = model.globalParameters || [];
|
||||
model.globalParameters.push(newParameter);
|
||||
return newParameter;
|
||||
}
|
||||
} else {
|
||||
// always create the parameter
|
||||
const paramSchema = parameter.param.sourceProperty ? getSchemaForType(psContext, parameter.param.sourceProperty) : getSchemaForType(psContext, parameter.param);
|
||||
const newParameter = new Parameter(parameter.name, getDoc(psContext.program, parameter.param) || "", paramSchema);
|
||||
newParameter.language.default.serializedName = parameter.name;
|
||||
newParameter.protocol.http = newParameter.protocol.http ?? new Protocol();
|
||||
newParameter.protocol.http.in = parameter.type;
|
||||
// ToDo, we need to support x-ms-client is specified.
|
||||
newParameter.implementation = ImplementationLocation.Method;
|
||||
newParameter.required = !parameter.param.optional;
|
||||
return newParameter;
|
||||
}
|
||||
}
|
||||
|
||||
// function addGlobalParameter(parameter: Parameter, model: PwshModel) {
|
||||
// if ((parameter.protocol?.http?.in == "query" && parameter.language.default.name === "api-version")
|
||||
// || (parameter.protocol?.http?.in == "path" && parameter.language.default.name === "subscriptionId")) {
|
||||
// model.globalParameters = model.globalParameters || [];
|
||||
// model.globalParameters.push(parameter);
|
||||
// }
|
||||
// }
|
||||
function getServiceInfo(program: Program): Info {
|
||||
const defaultService = getDefaultService(program);
|
||||
const info = new Info(defaultService?.title || "");
|
||||
info.description = defaultService && getDoc(program, defaultService.type);
|
||||
return info;
|
||||
}
|
||||
|
||||
function getLanguageDefault(program: Program, emitterOptions: PSOptions): Language {
|
||||
const defaultLanguage: Language = {
|
||||
name:
|
||||
emitterOptions.packageDetails?.name ??
|
||||
// Todo: may need to normalize the name
|
||||
pascalCase(deconstruct(emitterOptions?.title ?? getDefaultService(program)?.title ?? "")),
|
||||
description: ''
|
||||
};
|
||||
return defaultLanguage;
|
||||
}
|
||||
// export function transformUrlInfo(dpgContext: SdkContext): UrlInfo | undefined {
|
||||
// const program = dpgContext.program;
|
||||
// const serviceNs = getDefaultService(program)?.type;
|
||||
// let endpoint = undefined;
|
||||
// const urlParameters: PathParameter[] = [];
|
||||
// if (serviceNs) {
|
||||
// const host = getServers(program, serviceNs);
|
||||
// if (host?.[0]?.url) {
|
||||
// endpoint = host[0].url;
|
||||
// }
|
||||
// if (host && host?.[0] && host?.[0]?.parameters) {
|
||||
// // Currently we only support one parameter in the servers definition
|
||||
// for (const key of host[0].parameters.keys()) {
|
||||
// const property = host?.[0]?.parameters.get(key);
|
||||
// const type = property?.type;
|
||||
|
||||
// if (!property || !type) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// const schema = getSchemaForType(dpgContext, type, {
|
||||
// usage: [SchemaContext.Exception, SchemaContext.Input],
|
||||
// needRef: false,
|
||||
// relevantProperty: property
|
||||
// });
|
||||
// urlParameters.push({
|
||||
// oriName: key,
|
||||
// name: normalizeName(key, NameType.Parameter, true),
|
||||
// type: getTypeName(schema),
|
||||
// description:
|
||||
// (getDoc(program, property) &&
|
||||
// getFormattedPropertyDoc(program, property, schema, " ")) ??
|
||||
// getFormattedPropertyDoc(program, type, schema, " " /* sperator*/),
|
||||
// value: predictDefaultValue(dpgContext, host?.[0]?.parameters.get(key))
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (endpoint && urlParameters.length > 0) {
|
||||
// for (const param of urlParameters) {
|
||||
// if (param.oriName) {
|
||||
// const regexp = new RegExp(`{${param.oriName}}`, "g");
|
||||
// endpoint = endpoint.replace(regexp, `{${param.name}}`);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // Set the default value if missing endpoint parameter
|
||||
// if (endpoint == undefined && urlParameters.length === 0) {
|
||||
// endpoint = "{endpoint}";
|
||||
// urlParameters.push({
|
||||
// name: "endpoint",
|
||||
// type: "string"
|
||||
// });
|
||||
// }
|
||||
// return { endpoint, urlParameters };
|
||||
// }
|
|
@ -0,0 +1,44 @@
|
|||
import { Program, EmitContext, listServices, emitFile, resolvePath } from "@typespec/compiler";
|
||||
import { serialize } from '@azure-tools/codegen';
|
||||
import { createSdkContext } from "@azure-tools/typespec-client-generator-core";
|
||||
// Following files finally should be imported from @autorest/powershell
|
||||
// import { ModelState } from "./powershell/utils/model-state.js";
|
||||
// import { PwshModel } from "./powershell/utils/PwshModel.js";
|
||||
import { getClients } from "./utils/clientUtils.js";
|
||||
import { transformPwshModel } from "./convertor/convertor.js";
|
||||
import { PSOptions } from "./types/interfaces.js";
|
||||
import { generatePwshModule } from "@autorest/powershell";
|
||||
|
||||
export async function $onEmit(context: EmitContext) {
|
||||
const program: Program = context.program;
|
||||
// Emitter options from tspconfig.json
|
||||
// ToDo: need to implement a configuration class for AzPS
|
||||
const emitterOptions: PSOptions = context.options;
|
||||
|
||||
const PsContext = createSdkContext(
|
||||
context,
|
||||
"@azure-tools/typespec-powershell"
|
||||
);
|
||||
|
||||
// const pwshModel = new PwshModel("");
|
||||
// const services = listServices(program);
|
||||
// if (!context.program.compilerOptions.noEmit) {
|
||||
// await emitFile(context.program, {
|
||||
// path: resolvePath(context.emitterOutputDir, "output.txt"),
|
||||
// content: "Hello world\n" + serialize(program.stateMap(Symbol("typescript-models-" + services[0].type.name + "Client"))),
|
||||
// });
|
||||
// }
|
||||
await generatePwshModel();
|
||||
async function generatePwshModel() {
|
||||
const clients = getClients(PsContext);
|
||||
for (const client of clients) {
|
||||
const model = await transformPwshModel(client, PsContext, emitterOptions);
|
||||
await emitFile(context.program, {
|
||||
path: resolvePath(context.emitterOutputDir, `${client.name}.yaml`),
|
||||
content: serialize(model),
|
||||
});
|
||||
generatePwshModule(model, emitterOptions);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export { $onEmit } from "./emitter.js";
|
||||
export { $lib } from "./lib.js";
|
|
@ -0,0 +1,8 @@
|
|||
import { createTypeSpecLibrary } from "@typespec/compiler";
|
||||
|
||||
export const $lib = createTypeSpecLibrary({
|
||||
name: "powershell-ts",
|
||||
diagnostics: {},
|
||||
});
|
||||
|
||||
export const { reportDiagnostic, createDiagnostic } = $lib;
|
|
@ -0,0 +1,8 @@
|
|||
import { resolvePath } from "@typespec/compiler";
|
||||
import { createTestLibrary, TypeSpecTestLibrary } from "@typespec/compiler/testing";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
export const PowershellTsTestLibrary: TypeSpecTestLibrary = createTestLibrary({
|
||||
name: "powershell-ts",
|
||||
packageRoot: resolvePath(fileURLToPath(import.meta.url), "../../../../"),
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
export interface PSOptions {
|
||||
packageDetails?: PackageDetails;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface PackageDetails {
|
||||
name: string;
|
||||
scopeName?: string;
|
||||
nameWithoutScope?: string;
|
||||
description?: string;
|
||||
version?: string;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { SdkClient, SdkContext } from "@azure-tools/typespec-client-generator-core";
|
||||
import {
|
||||
Namespace,
|
||||
getNamespaceFullName,
|
||||
listServices
|
||||
} from "@typespec/compiler";
|
||||
|
||||
export function getClients(psContext: SdkContext): SdkClient[] {
|
||||
const services = listServices(psContext.program);
|
||||
|
||||
return services.map((service) => {
|
||||
const clientName = service.type.name + "Client";
|
||||
return {
|
||||
kind: "SdkClient",
|
||||
name: clientName,
|
||||
service: service.type,
|
||||
type: service.type,
|
||||
arm: isArm(service.type),
|
||||
crossLanguageDefinitionId: `${getNamespaceFullName(
|
||||
service.type
|
||||
)}.${clientName}`
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function isArm(service: Namespace): boolean {
|
||||
return service.decorators.some(
|
||||
(decorator) => decorator.decorator.name === "$armProviderNamespace"
|
||||
);
|
||||
}
|
||||
|
||||
// export function isRLCMultiEndpoint(dpgContext: SdkContext): boolean {
|
||||
// return getRLCClients(dpgContext).length > 1;
|
||||
// }
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,80 @@
|
|||
import { getAllModels } from "@azure-tools/typespec-client-generator-core";
|
||||
import {
|
||||
Namespace,
|
||||
isGlobalNamespace,
|
||||
isService,
|
||||
Operation
|
||||
} from "@typespec/compiler";
|
||||
// import { SdkContext } from "./interfaces.js";
|
||||
import { SdkContext } from "@azure-tools/typespec-client-generator-core";
|
||||
|
||||
|
||||
export function getModelNamespaceName(
|
||||
dpgContext: SdkContext,
|
||||
namespace: Namespace
|
||||
): string[] {
|
||||
const result: string[] = [];
|
||||
namespace &&
|
||||
!isGlobalNamespace(dpgContext.program, namespace) &&
|
||||
!isService(dpgContext.program, namespace)
|
||||
? (result.push(...getModelNamespaceName(dpgContext, namespace.namespace!)),
|
||||
result.push(namespace.name))
|
||||
: result;
|
||||
return result;
|
||||
}
|
||||
|
||||
// export function getOperationNamespaceInterfaceName(
|
||||
// dpgContext: SdkContext,
|
||||
// operation: Operation
|
||||
// ): string[] {
|
||||
// const result: string[] = [];
|
||||
// if (
|
||||
// dpgContext.rlcOptions?.hierarchyClient === false &&
|
||||
// dpgContext.rlcOptions?.enableOperationGroup !== true
|
||||
// ) {
|
||||
// return result;
|
||||
// }
|
||||
// if (operation.interface) {
|
||||
// if (
|
||||
// dpgContext.rlcOptions?.enableOperationGroup === true &&
|
||||
// dpgContext.rlcOptions?.hierarchyClient === false
|
||||
// ) {
|
||||
// result.push(operation.interface.name);
|
||||
// return result;
|
||||
// }
|
||||
// if (
|
||||
// operation.interface.namespace &&
|
||||
// !isGlobalNamespace(dpgContext.program, operation.interface.namespace) &&
|
||||
// !isService(dpgContext.program, operation.interface.namespace)
|
||||
// ) {
|
||||
// result.push(
|
||||
// ...getModelNamespaceName(dpgContext, operation.interface.namespace)
|
||||
// );
|
||||
// }
|
||||
// result.push(operation.interface.name);
|
||||
// } else if (operation.namespace) {
|
||||
// !isGlobalNamespace(dpgContext.program, operation.namespace) &&
|
||||
// !isService(dpgContext.program, operation.namespace)
|
||||
// ? (result.push(
|
||||
// ...getModelNamespaceName(dpgContext, operation.namespace.namespace!)
|
||||
// ),
|
||||
// result.push(operation.namespace.name))
|
||||
// : result;
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
export function detectModelConflicts(dpgContext: SdkContext) {
|
||||
const allModels = getAllModels(dpgContext);
|
||||
const nameSet = new Set<string>();
|
||||
for (const model of allModels) {
|
||||
if (model.name === "") {
|
||||
continue;
|
||||
}
|
||||
if (nameSet.has(model.name)) {
|
||||
return true;
|
||||
}
|
||||
nameSet.add(model.name);
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,543 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
// import {
|
||||
// NameType,
|
||||
// Paths,
|
||||
// ResponseMetadata,
|
||||
// ResponseTypes,
|
||||
// getLroLogicalResponseName,
|
||||
// getResponseTypeName,
|
||||
// normalizeName
|
||||
// } from "@azure-tools/rlc-common";
|
||||
import {
|
||||
getProjectedName,
|
||||
ignoreDiagnostics,
|
||||
Model,
|
||||
Operation,
|
||||
Program,
|
||||
Type
|
||||
} from "@typespec/compiler";
|
||||
import {
|
||||
HttpOperation,
|
||||
HttpOperationParameter,
|
||||
HttpOperationResponse,
|
||||
HttpStatusCodesEntry,
|
||||
getHttpOperation
|
||||
} from "@typespec/http";
|
||||
import {
|
||||
getLroMetadata,
|
||||
getPagedResult,
|
||||
PagedResultMetadata
|
||||
} from "@azure-tools/typespec-azure-core";
|
||||
import {
|
||||
SdkClient,
|
||||
listOperationGroups,
|
||||
listOperationsInOperationGroup,
|
||||
SdkContext
|
||||
} from "@azure-tools/typespec-client-generator-core";
|
||||
// import {
|
||||
// OperationLroDetail,
|
||||
// OPERATION_LRO_LOW_PRIORITY,
|
||||
// OPERATION_LRO_HIGH_PRIORITY
|
||||
// } from "@azure-tools/rlc-common";
|
||||
// import { isByteOrByteUnion } from "./modelUtils.js";
|
||||
// import { SdkContext } from "./interfaces.js";
|
||||
// import { getOperationNamespaceInterfaceName } from "./namespaceUtils.js";
|
||||
// import { KnownMediaType, knownMediaType } from "./mediaTypes.js";
|
||||
|
||||
// Sorts the responses by status code
|
||||
export function sortedOperationResponses(responses: HttpOperationResponse[]) {
|
||||
return responses.sort((a, b) => {
|
||||
if (a.statusCodes === "*") {
|
||||
return 1;
|
||||
}
|
||||
if (b.statusCodes === "*") {
|
||||
return -1;
|
||||
}
|
||||
const aStatus =
|
||||
typeof a.statusCodes === "number" ? a.statusCodes : a.statusCodes.start;
|
||||
const bStatus =
|
||||
typeof b.statusCodes === "number" ? b.statusCodes : b.statusCodes.start;
|
||||
return aStatus - bStatus;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function computes all the response types error and success
|
||||
* an operation can end up returning.
|
||||
*/
|
||||
// ToDo: get response by xiaogang
|
||||
// export function getOperationResponseTypes(
|
||||
// dpgContext: SdkContext,
|
||||
// operation: HttpOperation
|
||||
// ): ResponseTypes {
|
||||
// const returnTypes: ResponseTypes = {
|
||||
// error: [],
|
||||
// success: []
|
||||
// };
|
||||
// function getResponseType(responses: HttpOperationResponse[]) {
|
||||
// return responses.map((r) => {
|
||||
// const statusCode = getOperationStatuscode(r);
|
||||
// const responseName = getResponseTypeName(
|
||||
// getOperationGroupName(dpgContext, operation),
|
||||
// getOperationName(dpgContext.program, operation.operation),
|
||||
// statusCode
|
||||
// );
|
||||
// return responseName;
|
||||
// });
|
||||
// }
|
||||
// if (operation.responses && operation.responses.length) {
|
||||
// returnTypes.error = getResponseType(
|
||||
// operation.responses.filter((r) => isDefaultStatusCode(r.statusCodes))
|
||||
// );
|
||||
// returnTypes.success = getResponseType(
|
||||
// operation.responses.filter((r) => isDefinedStatusCode(r.statusCodes))
|
||||
// );
|
||||
// }
|
||||
// return returnTypes;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Extracts all success or defined status codes for a give operation
|
||||
*/
|
||||
export function getOperationSuccessStatus(operation: HttpOperation): string[] {
|
||||
const responses = operation.responses ?? [];
|
||||
const status: string[] = [];
|
||||
|
||||
for (const response of responses) {
|
||||
if (isDefinedStatusCode(response.statusCodes)) {
|
||||
status.push(getOperationStatuscode(response));
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
export function getOperationStatuscode(
|
||||
response: HttpOperationResponse
|
||||
): string {
|
||||
const statusCodes = response.statusCodes;
|
||||
if (statusCodes === "*") {
|
||||
return "default";
|
||||
} else if (typeof statusCodes === "number") {
|
||||
return String(statusCodes);
|
||||
} else {
|
||||
// FIXME - this is a hack to get the first status code
|
||||
// https://github.com/Azure/autorest.typescript/issues/2063
|
||||
return String(statusCodes.start);
|
||||
}
|
||||
}
|
||||
|
||||
// export function getOperationGroupName(
|
||||
// dpgContext: SdkContext,
|
||||
// route?: HttpOperation
|
||||
// ): string;
|
||||
// export function getOperationGroupName(
|
||||
// dpgContext: SdkContext,
|
||||
// operation?: Operation
|
||||
// ): string;
|
||||
// export function getOperationGroupName(
|
||||
// dpgContext: SdkContext,
|
||||
// operationOrRoute?: Operation | HttpOperation
|
||||
// ) {
|
||||
// if (!dpgContext.rlcOptions?.enableOperationGroup || !operationOrRoute) {
|
||||
// return "";
|
||||
// }
|
||||
// // If this is a HttpOperation
|
||||
// if ((operationOrRoute as any).kind !== "Operation") {
|
||||
// operationOrRoute = (operationOrRoute as HttpOperation).operation;
|
||||
// }
|
||||
// const operation = operationOrRoute as Operation;
|
||||
// const namespaceNames = getOperationNamespaceInterfaceName(
|
||||
// dpgContext,
|
||||
// operation
|
||||
// );
|
||||
|
||||
// return namespaceNames
|
||||
// .map((name) => {
|
||||
// return normalizeName(name, NameType.Interface, true);
|
||||
// })
|
||||
// .join("");
|
||||
// }
|
||||
|
||||
// export function getOperationName(program: Program, operation: Operation) {
|
||||
// const projectedOperationName = getProjectedName(program, operation, "json");
|
||||
|
||||
// return normalizeName(
|
||||
// projectedOperationName ?? operation.name,
|
||||
// NameType.Interface,
|
||||
// true
|
||||
// );
|
||||
// }
|
||||
|
||||
export function isDefaultStatusCode(statusCodes: HttpStatusCodesEntry) {
|
||||
return statusCodes === "*";
|
||||
}
|
||||
|
||||
export function isDefinedStatusCode(statusCodes: HttpStatusCodesEntry) {
|
||||
return statusCodes !== "*";
|
||||
}
|
||||
// ToDo: Binary playload by xiaogang
|
||||
// export function isBinaryPayload(
|
||||
// dpgContext: SdkContext,
|
||||
// body: Type,
|
||||
// contentType: string | string[]
|
||||
// ) {
|
||||
// const knownMediaTypes: KnownMediaType[] = (
|
||||
// Array.isArray(contentType) ? contentType : [contentType]
|
||||
// ).map((ct) => knownMediaType(ct));
|
||||
// for (const type of knownMediaTypes) {
|
||||
// if (type === KnownMediaType.Binary && isByteOrByteUnion(dpgContext, body)) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
export function isLongRunningOperation(
|
||||
program: Program,
|
||||
operation: HttpOperation
|
||||
) {
|
||||
return Boolean(getLroMetadata(program, operation.operation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if we have a client-level LRO overloading
|
||||
* @param pathDictionary
|
||||
* @returns
|
||||
*/
|
||||
// export function getClientLroOverload(pathDictionary: Paths) {
|
||||
// let lroCounts = 0,
|
||||
// allowCounts = 0;
|
||||
// for (const details of Object.values(pathDictionary)) {
|
||||
// for (const methodDetails of Object.values(details.methods)) {
|
||||
// const lroDetail = methodDetails[0].operationHelperDetail?.lroDetails;
|
||||
// if (lroDetail?.isLongRunning) {
|
||||
// lroCounts++;
|
||||
// if (!lroDetail.operationLroOverload) {
|
||||
// return false;
|
||||
// }
|
||||
// allowCounts++;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return Boolean(lroCounts > 0 && lroCounts === allowCounts);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Check if we have an operation-level overloading
|
||||
* @param program
|
||||
* @param operation The operation detail
|
||||
* @param existingResponseTypes auxilary param for current response types
|
||||
* @param existingResponses auxilary param for raw response data
|
||||
* @returns
|
||||
*/
|
||||
// export function getOperationLroOverload(
|
||||
// program: Program,
|
||||
// operation: HttpOperation,
|
||||
// existingResponseTypes?: ResponseTypes,
|
||||
// existingResponses?: ResponseMetadata[]
|
||||
// ) {
|
||||
// const metadata = getLroMetadata(program, operation.operation);
|
||||
// if (!metadata) {
|
||||
// return false;
|
||||
// }
|
||||
// const hasSuccessReturn = existingResponses?.filter((r) =>
|
||||
// r.statusCode.startsWith("20")
|
||||
// );
|
||||
// if (existingResponseTypes?.success || hasSuccessReturn) {
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Extract the operation LRO details
|
||||
* @param program
|
||||
* @param operation Operation detail
|
||||
* @param responsesTypes Calculated response types
|
||||
* @param operationGroupName Operation group name
|
||||
* @returns
|
||||
*/
|
||||
// export function extractOperationLroDetail(
|
||||
// program: Program,
|
||||
// operation: HttpOperation,
|
||||
// responsesTypes: ResponseTypes,
|
||||
// operationGroupName: string
|
||||
// ): OperationLroDetail {
|
||||
// let logicalResponseTypes: ResponseTypes | undefined;
|
||||
|
||||
// let precedence = OPERATION_LRO_LOW_PRIORITY;
|
||||
// const operationLroOverload = getOperationLroOverload(
|
||||
// program,
|
||||
// operation,
|
||||
// responsesTypes
|
||||
// );
|
||||
// if (operationLroOverload) {
|
||||
// logicalResponseTypes = {
|
||||
// error: responsesTypes.error,
|
||||
// success: [
|
||||
// getLroLogicalResponseName(
|
||||
// operationGroupName,
|
||||
// getOperationName(program, operation.operation)
|
||||
// )
|
||||
// ]
|
||||
// };
|
||||
// const metadata = getLroMetadata(program, operation.operation);
|
||||
// precedence =
|
||||
// metadata?.finalStep &&
|
||||
// metadata.finalStep.kind === "pollingSuccessProperty" &&
|
||||
// metadata?.finalStep.target &&
|
||||
// metadata?.finalStep?.target?.name === "result"
|
||||
// ? OPERATION_LRO_HIGH_PRIORITY
|
||||
// : OPERATION_LRO_LOW_PRIORITY;
|
||||
// }
|
||||
|
||||
// return {
|
||||
// isLongRunning: Boolean(getLroMetadata(program, operation.operation)),
|
||||
// logicalResponseTypes,
|
||||
// operationLroOverload,
|
||||
// precedence
|
||||
// };
|
||||
// }
|
||||
|
||||
export function hasPollingOperations(
|
||||
program: Program,
|
||||
client: SdkClient,
|
||||
dpgContext: SdkContext
|
||||
) {
|
||||
const clientOperations = listOperationsInOperationGroup(dpgContext, client);
|
||||
for (const clientOp of clientOperations) {
|
||||
const route = ignoreDiagnostics(getHttpOperation(program, clientOp));
|
||||
// ignore overload base operation
|
||||
if (route.overloads && route.overloads?.length > 0) {
|
||||
continue;
|
||||
}
|
||||
if (isLongRunningOperation(program, route)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const operationGroups = listOperationGroups(dpgContext, client, true);
|
||||
for (const operationGroup of operationGroups) {
|
||||
const operations = listOperationsInOperationGroup(
|
||||
dpgContext,
|
||||
operationGroup
|
||||
);
|
||||
for (const op of operations) {
|
||||
const route = ignoreDiagnostics(getHttpOperation(program, op));
|
||||
// ignore overload base operation
|
||||
if (route.overloads && route.overloads?.length > 0) {
|
||||
continue;
|
||||
}
|
||||
if (isLongRunningOperation(program, route)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isPagingOperation(program: Program, operation: HttpOperation) {
|
||||
for (const response of operation.responses) {
|
||||
const paged = extractPagedMetadataNested(program, response.type as Model);
|
||||
if (paged) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function hasPagingOperations(
|
||||
program: Program,
|
||||
client: SdkClient,
|
||||
dpgContext: SdkContext
|
||||
) {
|
||||
const clientOperations = listOperationsInOperationGroup(dpgContext, client);
|
||||
for (const clientOp of clientOperations) {
|
||||
const route = ignoreDiagnostics(getHttpOperation(program, clientOp));
|
||||
// ignore overload base operation
|
||||
if (route.overloads && route.overloads?.length > 0) {
|
||||
continue;
|
||||
}
|
||||
if (isPagingOperation(program, route)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const operationGroups = listOperationGroups(dpgContext, client, true);
|
||||
for (const operationGroup of operationGroups) {
|
||||
const operations = listOperationsInOperationGroup(
|
||||
dpgContext,
|
||||
operationGroup
|
||||
);
|
||||
for (const op of operations) {
|
||||
const route = ignoreDiagnostics(getHttpOperation(program, op));
|
||||
// ignore overload base operation
|
||||
if (route.overloads && route.overloads?.length > 0) {
|
||||
continue;
|
||||
}
|
||||
if (isPagingOperation(program, route)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function extractPagedMetadataNested(
|
||||
program: Program,
|
||||
type: Model
|
||||
): PagedResultMetadata | undefined {
|
||||
// This only works for `is Page<T>` not `extends Page<T>`.
|
||||
let paged = getPagedResult(program, type);
|
||||
if (paged) {
|
||||
return paged;
|
||||
}
|
||||
if (type.baseModel) {
|
||||
paged = getPagedResult(program, type.baseModel);
|
||||
}
|
||||
if (paged) {
|
||||
return paged;
|
||||
}
|
||||
const templateArguments = type.templateMapper?.args;
|
||||
if (templateArguments) {
|
||||
for (const argument of templateArguments) {
|
||||
const modelArgument = argument as Model;
|
||||
if (modelArgument) {
|
||||
paged = extractPagedMetadataNested(program, modelArgument);
|
||||
if (paged) {
|
||||
return paged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return paged;
|
||||
}
|
||||
|
||||
export function getSpecialSerializeInfo(
|
||||
paramType: string,
|
||||
paramFormat: string
|
||||
) {
|
||||
const hasMultiCollection = getHasMultiCollection(paramType, paramFormat);
|
||||
const hasPipeCollection = getHasPipeCollection(paramType, paramFormat);
|
||||
const hasSsvCollection = getHasSsvCollection(paramType, paramFormat);
|
||||
const hasTsvCollection = getHasTsvCollection(paramType, paramFormat);
|
||||
const hasCsvCollection = getHasCsvCollection(paramType, paramFormat);
|
||||
const descriptions = [];
|
||||
const collectionInfo = [];
|
||||
if (hasMultiCollection) {
|
||||
descriptions.push("buildMultiCollection");
|
||||
collectionInfo.push("multi");
|
||||
}
|
||||
if (hasSsvCollection) {
|
||||
descriptions.push("buildSsvCollection");
|
||||
collectionInfo.push("ssv");
|
||||
}
|
||||
|
||||
if (hasTsvCollection) {
|
||||
descriptions.push("buildTsvCollection");
|
||||
collectionInfo.push("tsv");
|
||||
}
|
||||
|
||||
if (hasPipeCollection) {
|
||||
descriptions.push("buildPipeCollection");
|
||||
collectionInfo.push("pipe");
|
||||
}
|
||||
|
||||
if (hasCsvCollection) {
|
||||
descriptions.push("buildCsvCollection");
|
||||
collectionInfo.push("csv");
|
||||
}
|
||||
return {
|
||||
hasMultiCollection,
|
||||
hasPipeCollection,
|
||||
hasSsvCollection,
|
||||
hasTsvCollection,
|
||||
hasCsvCollection,
|
||||
descriptions,
|
||||
collectionInfo
|
||||
};
|
||||
}
|
||||
|
||||
function getHasMultiCollection(paramType: string, paramFormat: string) {
|
||||
return (
|
||||
(paramType === "query" || paramType === "header") && paramFormat === "multi"
|
||||
);
|
||||
}
|
||||
function getHasSsvCollection(paramType: string, paramFormat: string) {
|
||||
return paramType === "query" && paramFormat === "ssv";
|
||||
}
|
||||
|
||||
function getHasTsvCollection(paramType: string, paramFormat: string) {
|
||||
return paramType === "query" && paramFormat === "tsv";
|
||||
}
|
||||
|
||||
function getHasCsvCollection(paramType: string, paramFormat: string) {
|
||||
return paramType === "header" && paramFormat === "csv";
|
||||
}
|
||||
|
||||
function getHasPipeCollection(paramType: string, paramFormat: string) {
|
||||
return paramType === "query" && paramFormat === "pipes";
|
||||
}
|
||||
|
||||
export function hasCollectionFormatInfo(
|
||||
paramType: string,
|
||||
paramFormat: string
|
||||
) {
|
||||
return (
|
||||
getHasMultiCollection(paramType, paramFormat) ||
|
||||
getHasSsvCollection(paramType, paramFormat) ||
|
||||
getHasTsvCollection(paramType, paramFormat) ||
|
||||
getHasCsvCollection(paramType, paramFormat) ||
|
||||
getHasPipeCollection(paramType, paramFormat)
|
||||
);
|
||||
}
|
||||
|
||||
export function getCollectionFormatHelper(
|
||||
paramType: string,
|
||||
paramFormat: string
|
||||
) {
|
||||
const detail = getSpecialSerializeInfo(paramType, paramFormat);
|
||||
return detail.descriptions.length > 0 ? detail.descriptions[0] : undefined;
|
||||
}
|
||||
|
||||
export function getCustomRequestHeaderNameForOperation(
|
||||
route: HttpOperation
|
||||
): string | undefined {
|
||||
const params = route.parameters.parameters.filter(
|
||||
isCustomClientRequestIdParam
|
||||
);
|
||||
if (params.length > 0) {
|
||||
return "client-request-id";
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function isCustomClientRequestIdParam(param: HttpOperationParameter) {
|
||||
return (
|
||||
param.type === "header" && param.name.toLowerCase() === "client-request-id"
|
||||
);
|
||||
}
|
||||
|
||||
export function isIgnoredHeaderParam(param: HttpOperationParameter) {
|
||||
return (
|
||||
isCustomClientRequestIdParam(param) ||
|
||||
(param.type === "header" &&
|
||||
["return-client-request-id", "ocp-date"].includes(
|
||||
param.name.toLowerCase()
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
export function parseNextLinkName(
|
||||
paged: PagedResultMetadata
|
||||
): string | undefined {
|
||||
return paged.nextLinkProperty?.name;
|
||||
}
|
||||
|
||||
export function parseItemName(paged: PagedResultMetadata): string | undefined {
|
||||
// TODO: support the nested item names
|
||||
return (paged.itemsSegments ?? [])[0];
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
import {
|
||||
SdkClient,
|
||||
SdkContext,
|
||||
listOperationGroups,
|
||||
listOperationsInOperationGroup
|
||||
} from "@azure-tools/typespec-client-generator-core";
|
||||
import { ignoreDiagnostics, Model, Program, Type } from "@typespec/compiler";
|
||||
import { getHttpOperation, HttpOperation } from "@typespec/http";
|
||||
import {
|
||||
hasPagingOperations,
|
||||
extractPagedMetadataNested,
|
||||
parseNextLinkName,
|
||||
parseItemName
|
||||
} from "../utils/operationUtil.js";
|
||||
|
||||
|
||||
const pageableOperationsKey = Symbol("pageable");
|
||||
export function getPageable(
|
||||
program: Program,
|
||||
entity: Type
|
||||
): string | undefined {
|
||||
return program.stateMap(pageableOperationsKey).get(entity);
|
||||
}
|
||||
|
||||
export function extractPageDetailFromCore(
|
||||
program: Program,
|
||||
client: SdkClient,
|
||||
dpgContext: SdkContext
|
||||
) {
|
||||
if (!hasPagingOperations(program, client, dpgContext)) {
|
||||
return;
|
||||
}
|
||||
const nextLinks = new Set<string>();
|
||||
const itemNames = new Set<string>();
|
||||
// Add default values
|
||||
nextLinks.add("nextLink");
|
||||
itemNames.add("value");
|
||||
const clientOperations = listOperationsInOperationGroup(dpgContext, client);
|
||||
for (const clientOp of clientOperations) {
|
||||
const route = ignoreDiagnostics(getHttpOperation(program, clientOp));
|
||||
// ignore overload base operation
|
||||
if (route.overloads && route.overloads?.length > 0) {
|
||||
continue;
|
||||
}
|
||||
extractPageDetailFromCoreForRoute(route);
|
||||
}
|
||||
const operationGroups = listOperationGroups(dpgContext, client, true);
|
||||
for (const operationGroup of operationGroups) {
|
||||
const operations = listOperationsInOperationGroup(
|
||||
dpgContext,
|
||||
operationGroup
|
||||
);
|
||||
for (const op of operations) {
|
||||
const route = ignoreDiagnostics(getHttpOperation(program, op));
|
||||
// ignore overload base operation
|
||||
if (route.overloads && route.overloads?.length > 0) {
|
||||
continue;
|
||||
}
|
||||
extractPageDetailFromCoreForRoute(route);
|
||||
}
|
||||
}
|
||||
|
||||
function extractPageDetailFromCoreForRoute(route: HttpOperation) {
|
||||
for (const response of route.responses) {
|
||||
const paged = extractPagedMetadataNested(program, response.type as Model);
|
||||
if (paged) {
|
||||
const nextLinkName = parseNextLinkName(paged);
|
||||
if (nextLinkName) {
|
||||
nextLinks.add(nextLinkName);
|
||||
}
|
||||
const itemName = parseItemName(paged);
|
||||
if (itemName) {
|
||||
itemNames.add(itemName);
|
||||
}
|
||||
// Once we find paged metadata, we don't need to processs any further.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there are more than one options for nextLink and item names we need to generate a
|
||||
// more complex pagination helper.
|
||||
const isComplexPaging = nextLinks.size > 1 || itemNames.size > 1;
|
||||
return {
|
||||
hasPaging: true,
|
||||
pageDetails: {
|
||||
itemNames: [...itemNames],
|
||||
nextLinkNames: [...nextLinks],
|
||||
isComplexPaging
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { strictEqual } from "node:assert";
|
||||
import { describe, it } from "vitest";
|
||||
import { emit } from "./test-host.js";
|
||||
|
||||
describe("hello", () => {
|
||||
it("emit output.txt with content hello world", async () => {
|
||||
const results = await emit(`op test(): void;`);
|
||||
strictEqual(results["output.txt"], "Hello world\n");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
import { Diagnostic, resolvePath } from "@typespec/compiler";
|
||||
import {
|
||||
createTestHost,
|
||||
createTestWrapper,
|
||||
expectDiagnosticEmpty,
|
||||
} from "@typespec/compiler/testing";
|
||||
import { PowershellTsTestLibrary } from "../src/testing/index.js";
|
||||
|
||||
export async function createPowershellTsTestHost() {
|
||||
return createTestHost({
|
||||
libraries: [PowershellTsTestLibrary],
|
||||
});
|
||||
}
|
||||
|
||||
export async function createPowershellTsTestRunner() {
|
||||
const host = await createPowershellTsTestHost();
|
||||
|
||||
return createTestWrapper(host, {
|
||||
compilerOptions: {
|
||||
noEmit: false,
|
||||
emit: ["powershell-ts"],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function emitWithDiagnostics(
|
||||
code: string
|
||||
): Promise<[Record<string, string>, readonly Diagnostic[]]> {
|
||||
const runner = await createPowershellTsTestRunner();
|
||||
await runner.compileAndDiagnose(code, {
|
||||
outputDir: "tsp-output",
|
||||
});
|
||||
const emitterOutputDir = "./tsp-output/powershell-ts";
|
||||
const files = await runner.program.host.readDir(emitterOutputDir);
|
||||
|
||||
const result: Record<string, string> = {};
|
||||
for (const file of files) {
|
||||
result[file] = (await runner.program.host.readFile(resolvePath(emitterOutputDir, file))).text;
|
||||
}
|
||||
return [result, runner.program.diagnostics];
|
||||
}
|
||||
|
||||
export async function emit(code: string): Promise<Record<string, string>> {
|
||||
const [result, diagnostics] = await emitWithDiagnostics(code);
|
||||
expectDiagnosticEmpty(diagnostics);
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": false,
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"lib": ["ES2022"],
|
||||
"rootDir": ".",
|
||||
"outDir": "dist",
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"skipLibCheck": true,
|
||||
/* Linting */
|
||||
"strict": true
|
||||
},
|
||||
"include": ["src", "test"]
|
||||
}
|
Загрузка…
Ссылка в новой задаче