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

Commit

Permalink
20200928 example directive (#96)
Browse files Browse the repository at this point in the history
* WIP

* set value

* dotPath

* add test

* add doc

* use exampleParameterPath

Co-authored-by: Changlong Liu <[email protected]>
  • Loading branch information
changlong-liu and Changlong Liu authored Oct 20, 2020
1 parent 293bdc4 commit 9ae64f3
Show file tree
Hide file tree
Showing 11 changed files with 358 additions and 6 deletions.
17 changes: 17 additions & 0 deletions doc/cli-directive.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ For groupName, operationName, parameterName, typeName, propertyName, usually you
- search for enum or enumValue
- 'choiceSchema' | 'enum': 'choiceName'
- 'choiceValue' | 'value': 'choiceName'
- search for example and path in it
- 'exampleName'
- 'path' | 'dotPath' | 'exampleParameterPath': 'dotPath'
- set:
- set anything property in the selected object(s)
- optional
Expand Down Expand Up @@ -91,6 +94,10 @@ For groupName, operationName, parameterName, typeName, propertyName, usually you
- add 'min-api: ..." under 'language->cli'
- max-api:
- add 'max-api: ..." under 'language->cli'
- value:
- set static value for example dotPath
- eval:
- set dynamic value for example dotPath with an evaluatable script

## How to troubleshooting
> Add --debug in your command line to have more intermedia output files for troubleshooting
Expand Down Expand Up @@ -215,6 +222,16 @@ cli:
op: CreateOrUpdate#Update
param: properties
cli-flatten: true
# set example content with static value
- where:
exampleName: TheExampleName
exampleParameterPath: parameters.hostPool.location
value: Shanghai
# set example content with dynamic script
- where:
exampleName: TheExampleName
exampleParameterPath: parameters.registration-info.expiration-time
eval: "var d = new Date(); d.setDate(d.getDate()+15); var ye = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(d); var mo = new Intl.DateTimeFormat('en', { month: '2-digit' }).format(d); var da = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(d); `${ye}-${mo}-${da}T00:00:00.000Z` "
```

58 changes: 58 additions & 0 deletions src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ export class Helper {
public static ToM4NodeType(node: M4Node): M4NodeType {
if (Helper.isOperationGroup(node))
return CliConst.SelectType.operationGroup;
else if (Helper.isExample(node))
return CliConst.SelectType.dotPath;
else if (Helper.isOperation(node))
return CliConst.SelectType.operation;
else if (Helper.isParameter(node))
Expand Down Expand Up @@ -568,6 +570,12 @@ export class Helper {
Helper.enumerateRequestParameters(group, op, paths, action, flag);
paths.pop();

paths.push('extensions');
paths.push('x-ms-examples');
Helper.enumerateExamples(group, op, paths, action, flag);
paths.pop();
paths.pop();

paths.pop();
}
}
Expand Down Expand Up @@ -631,6 +639,26 @@ export class Helper {
}
}

public static enumerateExamples(group: OperationGroup, op: Operation, paths: string[], action: (nodeDescriptor: CliCommonSchema.CodeModel.NodeDescriptor) => void, flag: CliCommonSchema.CodeModel.NodeTypeFlag): void {
const enumExample = isNullOrUndefined(flag) || ((flag & CliCommonSchema.CodeModel.NodeTypeFlag.dotPath) > 0);
if (!enumExample || isNullOrUndefined(op.extensions?.['x-ms-examples'])) return;
const cliKeyMissing = '<clikey-missing>';

for (let exampleName of Object.getOwnPropertyNames(op.extensions['x-ms-examples'])) {
const example = op.extensions['x-ms-examples'][exampleName];
paths.push(`['${exampleName}']`);
action({
operationGroupCliKey: NodeCliHelper.getCliKey(group, cliKeyMissing),
operationCliKey: NodeCliHelper.getCliKey(op, cliKeyMissing),
parent: op.extensions['x-ms-examples'],
target: example,
targetIndex: -1,
exampleName: exampleName,
});
paths.pop();
}
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
public static isOperationGroup(o: any): boolean {
if (isNullOrUndefined(o)) {
Expand Down Expand Up @@ -822,6 +850,20 @@ export class Helper {
return false;
}

public static isExample(o: any): boolean {
if (isNullOrUndefined(o)) {
return false;
}
if (o.__proto__ !== Object.prototype) {
return false;
}
const props = Object.getOwnPropertyNames(o);
if (props.find((prop) => prop === 'parameters') && props.find((prop) => prop === 'responses') && props.length==2) {
return true;
}
return false;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
public static isSchema(o: any): boolean {
if (isNullOrUndefined(o)) {
Expand Down Expand Up @@ -873,4 +915,20 @@ export class Helper {
return m4Path;
}

public static setPathValue (obj, path, value) {
if (Object(obj) !== obj) return obj; // When obj is not an object
// If not yet an array, get the keys from the string-path
if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || [];
path.slice(0,-1).reduce((a, c, i) => // Iterate all of them except the last one
Object(a[c]) === a[c] // Does the key exist and is its value an object?
// Yes: then follow that path
? a[c]
// No: create the key. Is the next key a potential array-index?
: a[c] = Math.abs(path[i+1])>>0 === +path[i+1]
? [] // Yes: assign a new array object
: {}, // No: assign a new plain object
obj)[path[path.length-1]] = value; // Finally assign the value to the last key
return obj; // Return the top-level object to allow chaining
};

}
2 changes: 1 addition & 1 deletion src/plugins/m4namer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export class M4CommonNamer {

private retrieveCodeModel(model: CodeModel): void {
Helper.enumerateCodeModel(model, (n) => {
if (!isNullOrUndefined(n.target.language['cli'])) {
if (!isNullOrUndefined(n.target.language?.['cli'])) {
// log path for code model
NodeCliHelper.setCliPath(n.target, n.nodePath);
}
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/modelerPostProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class ModelerPostProcessor {

private retrieveCodeModel(model: CodeModel): void {
Helper.enumerateCodeModel(model, (n) => {
if (!isNullOrUndefined(n.target.language['cli'])) {
if (!isNullOrUndefined(n.target.language?.['cli'])) {
// In case cli is shared by multiple instances during modelerfour, do deep copy
n.target.language['cli'] = CopyHelper.deepCopy(n.target.language['cli']);

Expand Down
22 changes: 22 additions & 0 deletions src/plugins/modifier/cliDirectiveAction.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isNullOrUndefined, isArray } from "util";
import { CliCommonSchema, CliConst } from "../../schema";
import { NodeHelper, NodeCliHelper, NodeExtensionHelper } from "../../nodeHelper";
import { Helper } from "../../helper";


function validateDirective(directive: CliCommonSchema.CliDirective.Directive | string, name: string): void {
Expand Down Expand Up @@ -70,6 +71,12 @@ export abstract class Action {
case 'hitcount':
arr.push(new ActionHitCount(value));
break;
case 'value':
arr.push(new ActionSetValue(value, directive.where.dotPath));
break;
case 'eval':
arr.push(new ActionSetValue(eval(value), directive.where.dotPath));
break;
default:
// TODO: better to log instead of throw here?
throw Error(`Unknown directive operation: '${key}'`);
Expand Down Expand Up @@ -214,3 +221,18 @@ export class ActionReplace extends Action {
}
}
}


export class ActionSetValue extends Action {

// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(private directiveValue: CliCommonSchema.CliDirective.ValueClause, private dotPath: string) {
super();
}

public process(descriptor: CliCommonSchema.CodeModel.NodeDescriptor): void {
if (!isNullOrUndefined(this.dotPath)) {
Helper.setPathValue(descriptor.target, this.dotPath, this.directiveValue);
}
}
}
9 changes: 9 additions & 0 deletions src/plugins/modifier/cliDirectiveSelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export class NodeSelector {
property: ['prop'],
choiceSchema: ['enum', 'choice-schema'],
choiceValue: ['value', 'choice-value'],
exampleName: ['example-name'],
dotPath: ['path', 'dot-path', 'example-parameter-path'],
};

for (const key in alias) {
Expand All @@ -43,6 +45,8 @@ export class NodeSelector {
this.selectType = CliConst.SelectType.choiceValue;
else if (!Helper.isEmptyString(this.where.choiceSchema))
this.selectType = CliConst.SelectType.choiceSchema;
else if (!Helper.isEmptyString(this.where.dotPath))
this.selectType = CliConst.SelectType.dotPath;
else
throw Error("SelectType missing in directive: " + JSON.stringify(this.where));
}
Expand Down Expand Up @@ -75,6 +79,11 @@ export class NodeSelector {
r = match(this.where.objectSchema, descriptor.objectSchemaCliKey) &&
match(this.where.property, descriptor.propertyCliKey);
break;
case CliConst.SelectType.dotPath:
r = match(this.where.operationGroup, descriptor.operationGroupCliKey) &&
match(this.where.operation, descriptor.operationCliKey) &&
match(this.where.exampleName, descriptor.exampleName);
break;
default:
throw Error(`Unknown select type: ${this.selectType}`);
}
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/namer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export class CommonNamer {

private retrieveCodeModel(model: CodeModel): void {
Helper.enumerateCodeModel(model, (n) => {
if (!isNullOrUndefined(n.target.language['cli'])) {
if (!isNullOrUndefined(n.target?.language?.['cli'])) {
// log path for code model
NodeCliHelper.setCliPath(n.target, n.nodePath);
}
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/prenamer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class PreNamer{

public async process(): Promise<void> {
Helper.enumerateCodeModel(this.session.model, (n) => {
if (!isNullOrUndefined(n.target.language.default.name))
if (!isNullOrUndefined(n.target.language?.default?.name))
NodeCliHelper.setCliKey(n.target, n.target.language.default.name);
});

Expand Down
9 changes: 7 additions & 2 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ChoiceValue, Metadata } from "@azure-tools/codemodel";

export type NamingStyle = 'camel' | 'pascal' | 'snake' | 'upper' | 'kebab' | 'space';
export type NamingType = 'parameter' | 'operation' | 'operationGroup' | 'property' | 'type' | 'choice' | 'choiceValue' | 'constant' | 'client';
export type M4NodeType = 'operationGroup' | 'operation' | 'parameter' | 'objectSchema' | 'property' | 'choiceSchema' | 'choiceValue';
export type M4NodeType = 'operationGroup' | 'operation' | 'parameter' | 'objectSchema' | 'property' | 'choiceSchema' | 'choiceValue' | 'dotPath';
export type LanguageType = 'cli' | 'default';
export type M4Node = Metadata | ChoiceValue;

Expand Down Expand Up @@ -73,6 +73,7 @@ export namespace CliConst {
static readonly property = 'property';
static readonly choiceSchema = 'choiceSchema';
static readonly choiceValue = 'choiceValue';
static readonly dotPath = 'dotPath';
}
}

Expand Down Expand Up @@ -117,6 +118,8 @@ export namespace CliCommonSchema {
property?: string;
choiceSchema?: string;
choiceValue?: string;
exampleName?: string;
dotPath?: string;
}

export interface FormatTableClause {
Expand Down Expand Up @@ -170,7 +173,8 @@ export namespace CliCommonSchema {
objectSchema = 8,
property = 16,
choiceSchema = 32,
choiceValue = 64
choiceValue = 64,
dotPath = 128,
}

export enum Complexity {
Expand Down Expand Up @@ -233,6 +237,7 @@ export namespace CliCommonSchema {
* cliKey: nextLink
**/
nodePath?: string;
exampleName?: string;
}
}
}
44 changes: 44 additions & 0 deletions test/test_directive_action_setValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { assert } from 'chai';
import 'mocha';
import { ActionSet, ActionSetProperty, ActionSetValue } from '../src/plugins/modifier/cliDirectiveAction';
import { M4Node } from '../src/schema';
import { Metadata } from "@azure-tools/codemodel";

describe('Test Directive - Action - setValue', function () {
var descriptor = {
parent: null,
targetIndex: -1,
target: null,
exampleName: 'Example1'
};

it('directive setProperty - string', () => {
let baseline = "someValue";
let action = new ActionSetValue(baseline, "a.b.c");

descriptor.target = new Metadata();
action.process(descriptor);
assert.deepEqual(descriptor.target.a.b.c, baseline);
});

it('directive setProperty - number', () => {
let baseline = 123;
let action = new ActionSetValue(baseline, "a.b.c");

descriptor.target = new Metadata();
action.process(descriptor);
assert.deepEqual(descriptor.target.a.b.c, baseline);
});

it('directive setProperty - object', () => {
let baseline = {
key: "someValue",
};
let action = new ActionSetValue(baseline, "a.b.c");

descriptor.target = new Metadata();
action.process(descriptor);
assert.deepEqual(descriptor.target.a.b.c, baseline);
});

});
Loading

0 comments on commit 9ae64f3

Please sign in to comment.