Skip to content
This repository has been archived by the owner on Dec 15, 2023. It is now read-only.

Commit

Permalink
Merge pull request #65 from shawncx/SupportSplitOperation
Browse files Browse the repository at this point in the history
Support split operation
  • Loading branch information
shawncx authored Jun 2, 2020
2 parents b14d4d0 + adac1e3 commit a3d3d56
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 66 deletions.
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ pipeline:
input: modelerfour
output-artifact: clicommon-prenamer

clicommon/pre/cli-complex-marker:
clicommon/cli-split-operation:
input: clicommon/cli-prenamer
output-artifact: clicommon-split-operation

clicommon/pre/cli-complex-marker:
input: clicommon/cli-split-operation
output-artifact: clicommon-complex-marker-pre

clicommon/cli-flatten-setter:
Expand Down Expand Up @@ -52,6 +56,7 @@ pipeline:
input:
- clicommon
- clicommon/cli-prenamer
- clicommon/cli-split-operation
- clicommon/cli-flatten-setter
#- clicommon/cli-poly-as-param-modifier
- clicommon/cli-poly-as-resource-modifier
Expand All @@ -65,6 +70,7 @@ scope-clicommon:
output-artifact:
- clicommon-output
- clicommon-prenamer
- clicommon-split-operation
- clicommon-flatten-setter
- clicommon-poly-as-resource-modifier
#- clicommon-poly-as-param-modifier
Expand Down Expand Up @@ -95,7 +101,7 @@ modelerfour:
LRO: LRO

cli:
#flatten:
# flatten:
# cli-flatten-set-enabled: true
# cli-flatten-all: true
# cli-flatten-payload: true
Expand All @@ -120,6 +126,27 @@ cli:
# cli-flatten-payload-max-poly-as-resource-prop-count: 8
# # max properties allowed from flatten of sub-class as param
# cli-flatten-payload-max-poly-as-param-prop-count: 8

# example for split-operation
# cli-directive:
# - where:
# group: OperationGroupName
# op: CreateOrUpdate
# split-operation-names:
# - Create
# - Update

split-operation:
# if true, operation with 'split-operation-names' will be splited into multiple
# operations with given names.
# Notice:
# 1. Splitted operation's key is in formate: <OriginalOperationName>#<SplitName>.
# For example, in above case, the splitted operation keys are 'CreateOrUpdate#Create'
# and 'CreateOrUpdate#Update'. To make direcitve works on splitted operation, please
# use the new key.
# 2. If operation with split name has already existed in operation group, you will get
# a warning and this split name will be skipped.
cli-split-operation-enabled: true
polymorphism:
# if true, polymorphism parameter with 'poly-resource' marked as true will be
# expanded into multiple operations for each subclasses
Expand Down
14 changes: 14 additions & 0 deletions doc/cli-directive.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ For groupName, operationName, parameterName, typeName, propertyName, usually you
- old: 'old_value'
- new: 'new_value'
- isRegex: true | false
- split-operation-names
- split operation into multiple operations with given names
- value format:
- opName1
- opName2
- ...

## How to troubleshooting
> Add --debug in your command line to have more intermedia output files for troubleshooting
Expand Down Expand Up @@ -190,5 +196,13 @@ cli:
enum: 'enumTyp'
value: 'enumValue'
alias: NewAlias
# split operation into multiple operations
- where:
group: OperationGroupName
op: CreateOrUpdate
split-operation-names:
- Create
- Update
```

61 changes: 61 additions & 0 deletions src/copyHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Request, Parameter, Operation, Schema } from "@azure-tools/codemodel";
import { isNullOrUndefined } from "util";

export class CopyHelper {

public static copyOperation(source: Operation, globalParameters?: Parameter[], customizedReqCopy?: (req: Request) => Request, customizedParamCopy?: (srcParam: Parameter) => Parameter): Operation {
const copy = new Operation(source.language.default.name, '', source);
copy.language = CopyHelper.deepCopy(source.language);
copy.extensions = CopyHelper.copy(source.extensions);
copy.parameters = source.parameters?.map((op) => {
if (globalParameters?.find((gp) => gp === op)) {
return op;
} else if (customizedParamCopy) {
return customizedParamCopy(op);
} else {
return CopyHelper.copyParameter(op)
}
});
copy.requests = source.requests?.map((req) => customizedReqCopy == null ? CopyHelper.copyRequest(req) : customizedReqCopy(req));
copy.updateSignatureParameters();
return copy;
}

public static copyRequest(source: Request, customizedParamCopy?: (param: Parameter) => Parameter): Request {
const copy = new Request(source);
copy.extensions = CopyHelper.copy(source.extensions);
copy.language = CopyHelper.deepCopy(source.language);
copy.parameters = copy.parameters?.map((p) => customizedParamCopy == null ? CopyHelper.copyParameter(p) : customizedParamCopy(p));
copy.updateSignatureParameters();
return copy;
}

public static copyParameter(source: Parameter, customizedSchema?: Schema): Parameter {
const copy = new Parameter(source.language.default.name, source.language.default.description, customizedSchema ?? source.schema, {
implementation: source.implementation,
extensions: {},
language: CopyHelper.deepCopy(source.language),
protocol: source.protocol,
});
for (const property in source) {
if (isNullOrUndefined(copy[property])) {
copy[property] = source[property];
}
}
return copy;
}

public static copy<T>(source: T): T {
if (source == null) {
return source;
}
return Object.assign({}, source);
}

public static deepCopy<T>(source: T): T {
if (source == null) {
return source;
}
return JSON.parse(JSON.stringify(source));
}
}
1 change: 1 addition & 0 deletions src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ export class Helper {
`${tab(2)}- operationName: ${generateCliValue(vv, 3)}` +
(isNullOrUndefined(NodeHelper.getPolyAsResourceParam(vv)) ? '' : `${NEW_LINE}${tab(3)}cli-poly-as-resource-subclass-param: ${NodeHelper.getCliKey(NodeHelper.getPolyAsResourceParam(vv), '<missing-clikey>')}`) +
(isNullOrUndefined(NodeHelper.getPolyAsResourceOriginalOperation(vv)) ? '' : `${NEW_LINE}${tab(3)}cli-poly-as-resource-original-operation: ${NodeHelper.getCliKey(NodeHelper.getPolyAsResourceOriginalOperation(vv), '<missing-clikey>')}`) +
(isNullOrUndefined(NodeHelper.getSplitOperationOriginalOperation(vv)) ? '' : `${NEW_LINE}${tab(3)}cli-split-operation-original-operation: ${NodeHelper.getCliKey(NodeHelper.getSplitOperationOriginalOperation(vv), '<missing-clikey>')}`) +
`${NEW_LINE}${tab(3)}parameters:${NEW_LINE}`.concat(
vv.parameters.map(vvv => `${tab(3)}- parameterName: ${generateCliValue(vvv, 4)}${generatePropertyFlattenValue(vvv, 4)}${generatePropertyReadonlyValue(vvv, 4)}${generateDiscriminatorValueForParam(vvv, 4)}${NEW_LINE}` +
(isNullOrUndefined(NodeHelper.getPolyAsResourceBaseSchema(vvv)) ? '' : `${tab(4)}cli-poly-as-resource-base-schema: ${NodeHelper.getCliKey(NodeHelper.getPolyAsResourceBaseSchema(vvv), '<baseSchemaCliKeyMissing>')}${NEW_LINE}`) +
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Helper } from './helper';
import { Modifier } from './plugins/modifier/modifier';
import { CommonNamer } from './plugins/namer';
import { processRequest as flattenSetter } from './plugins/flattenSetter/flattenSetter';
import { processRequest as splitOperation } from './plugins/splitOperation';
import { processRequest as preNamer } from './plugins/prenamer';
import { CliConst } from './schema';
import { processRequest as polyAsResourceModifier } from './plugins/polyAsResourceModifier';
Expand Down Expand Up @@ -42,6 +43,7 @@ extension.Add("clicommon", async host => {

extension.Add("cli-flatten-setter", flattenSetter);
extension.Add("cli-prenamer", preNamer);
extension.Add("cli-split-operation", splitOperation);
extension.Add("cli-poly-as-resource-modifier", polyAsResourceModifier);
extension.Add("cli-poly-as-param-modifier", polyAsParamModifier);
extension.Add("cli-complex-marker", complexMarker);
Expand Down
27 changes: 27 additions & 0 deletions src/nodeHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class NodeHelper {
private static readonly CLI_MARK: string = "cli-mark";
private static readonly CLI_IS_VISIBLE: string = "cli-is-visible";
private static readonly CLI_OPERATIONS: string = "cli-operations";
private static readonly CLI_OPERATION_SPLITTED = 'cli-operation-splitted';
private static readonly JSON: string = "json";
public static readonly FLATTEN_FLAG: string = 'x-ms-client-flatten';
public static readonly DISCRIMINATOR_FLAG: string = 'discriminator';
Expand All @@ -27,6 +28,8 @@ export class NodeHelper {
private static readonly POLY_AS_PARAM_BASE_SCHEMA = 'cli-poly-as-param-base-schema';
private static readonly POLY_AS_PARAM_ORIGINIAL_PARAMETER = 'cli-poly-as-param-original-parameter';
private static readonly POLY_AS_PARAM_EXPANDED = 'cli-poly-as-param-expanded';
private static readonly SPLIT_OPERATION_ORIGINAL_OPERATION = 'cli-split-operation-original-operation';
private static readonly SPLIT_OPERATION_NAMES = 'split-operation-names';

private static visitedKeyDict = {};

Expand Down Expand Up @@ -160,6 +163,22 @@ export class NodeHelper {
return isNullOrUndefined(node.language[NodeHelper.CLI]) ? '' : node.language[NodeHelper.CLI][NodeHelper.DESCRIPTION];
}

public static setCliOperationSplitted(op: Operation, value: boolean) {
NodeHelper.setCliProperty(op, NodeHelper.CLI_OPERATION_SPLITTED, value);
}

public static getCliOperationSplitted(op: Operation): boolean {
return NodeHelper.getCliProperty(op, NodeHelper.CLI_OPERATION_SPLITTED, () => null);
}

public static getCliSplitOperationNames(node: Operation): string[] {
return NodeHelper.getCliProperty(node, this.SPLIT_OPERATION_NAMES, () => null);
}

public static clearCliSplitOperationNames(node: Operation) {
NodeHelper.clearCliProperty(node, this.SPLIT_OPERATION_NAMES);
}

public static setPolyAsResource(node: Parameter, value: boolean) {
NodeHelper.setCliProperty(node, this.POLY_RESOURCE, value);
}
Expand Down Expand Up @@ -192,6 +211,14 @@ export class NodeHelper {
return NodeHelper.getExtensionsProperty(op, NodeHelper.POLY_AS_RESOURCE_ORIGINAL_OPERATION, null);
}

public static setSplitOperationOriginalOperation(op: Operation, ori: Operation) {
NodeHelper.setExtensionsProperty(op, NodeHelper.SPLIT_OPERATION_ORIGINAL_OPERATION, ori);
}

public static getSplitOperationOriginalOperation(op: Operation): Schema {
return NodeHelper.getExtensionsProperty(op, NodeHelper.SPLIT_OPERATION_ORIGINAL_OPERATION, null);
}

public static setPolyAsParamBaseSchema(param: Parameter, base: Schema) {
NodeHelper.setExtensionsProperty(param, NodeHelper.POLY_AS_PARAM_BASE_SCHEMA, base);
}
Expand Down
5 changes: 4 additions & 1 deletion src/plugins/modifier/cliDirectiveAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export abstract class Action {
case 'poly-resource':
arr.push(new ActionSetProperty(value, key, () => true));
break;
case 'split-operation-names':
arr.push(new ActionSetProperty(value, key, () => null));
break;
case 'delete':
arr.push(new ActionDelete(value));
break;
Expand Down Expand Up @@ -183,4 +186,4 @@ export class ActionReplace extends Action {
NodeHelper.setCliProperty(node, this.actionReplace.field, original.replace(regex, this.actionReplace.new));
}
}
}
}
74 changes: 11 additions & 63 deletions src/plugins/polyAsResourceModifier.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { Host, Session, startSession } from "@azure-tools/autorest-extension-base";
import { CodeModel, Request, codeModelSchema, Metadata, ObjectSchema, isObjectSchema, Property, Extensions, Scheme, ComplexSchema, Operation, OperationGroup, Parameter, VirtualParameter, ImplementationLocation } from "@azure-tools/codemodel";
import { isNullOrUndefined, isArray, isNull } from "util";
import { Host, Session } from "@azure-tools/autorest-extension-base";
import { CodeModel, Request, ObjectSchema, Operation, OperationGroup, Parameter } from "@azure-tools/codemodel";
import { isNullOrUndefined } from "util";
import { Helper } from "../helper";
import { CliConst, M4Node } from "../schema";
import { Dumper } from "../dumper";
import { Dictionary, values } from '@azure-tools/linq';
import { NodeHelper } from "../nodeHelper";
import { FlattenHelper } from "../flattenHelper";
import { CopyHelper } from "../copyHelper";


export class PolyAsResourceModifier {
Expand All @@ -22,81 +20,31 @@ export class PolyAsResourceModifier {
return (NodeHelper.isPolyAsResource(param));
}

/**
* a simple object clone by using Json serialize and parse
* @param obj
*/
private cloneObject<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj)) as T;
}

private cloneObjectTopLevel(obj: any) {
let r = {};
for (let key in obj) {
r[key] = obj[key];
}
return r;
}

private cloneOperationForSubclass(op: Operation, newDefaultName: string, newCliKey: string, newCliName: string, baseSchema: ObjectSchema, subSchema: ObjectSchema) {

let polyParam: Parameter = null;

let cloneParam = (p: Parameter): Parameter => {

const vp = new Parameter(p.language.default.name, p.language.default.description, p.schema === baseSchema ? subSchema : p.schema, {
implementation: p.implementation,
extensions: {},
language: this.cloneObject(p.language),
protocol: p.protocol,
});

for (let key in p)
if (isNullOrUndefined(vp[key]))
vp[key] = p[key];

const cloneParam = (p: Parameter): Parameter => {
const vp = CopyHelper.copyParameter(p, p.schema === baseSchema ? subSchema : p.schema);
if (p.schema === baseSchema) {
if (polyParam !== null)
if (polyParam !== null) {
throw Error(`Mulitple poly as resource Parameter found: 1) ${polyParam.language.default.name}, 2) ${p.language.default.name}`);
else {
} else {
polyParam = vp;
NodeHelper.setPolyAsResourceBaseSchema(vp, baseSchema);
}
}

return vp;
};

let cloneRequest = (req: Request): Request => {
let rr = new Request(req);
rr.extensions = this.cloneObjectTopLevel(rr.extensions);
rr.language = this.cloneObject(rr.language);
rr.parameters = rr.parameters.map(p => cloneParam(p));
rr.updateSignatureParameters();
return rr;
}

let op2 = new Operation(
newDefaultName,
'',
op
);
op2.language = this.cloneObject(op2.language);
const cloneRequest = (req: Request): Request => CopyHelper.copyRequest(req, cloneParam);

const op2 = CopyHelper.copyOperation(op, this.session.model.globalParameters, cloneRequest, cloneParam);
op2.language.default.name = newDefaultName;
NodeHelper.setCliName(op2, newCliName);
NodeHelper.setCliKey(op2, newCliKey);
op2.extensions = this.cloneObjectTopLevel(op2.extensions);
op2.parameters = op2.parameters.map(p => {
if (this.session.model.findGlobalParameter(pp => pp === p))
return p;
else
return cloneParam(p)
});
op2.requests = op2.requests.map(r => cloneRequest(r));
op2.updateSignatureParameters();
NodeHelper.setPolyAsResourceParam(op2, polyParam);
NodeHelper.setPolyAsResourceOriginalOperation(op2, op);
// Do we need to deep copy response? seems no need

return op2;
}
Expand Down
Loading

0 comments on commit a3d3d56

Please sign in to comment.