Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TypeSpec Validation] Refactor each rule into a separate "Rule" class #24983

Closed
mikeharder opened this issue Jul 26, 2023 · 0 comments · Fixed by #25101
Closed

[TypeSpec Validation] Refactor each rule into a separate "Rule" class #24983

mikeharder opened this issue Jul 26, 2023 · 0 comments · Fixed by #25101
Assignees
Labels
Spec PR Tools Tooling that runs in azure-rest-api-specs repo.

Comments

@mikeharder
Copy link
Member

mikeharder commented Jul 26, 2023

To improve code organization and ease of maintenance, I think the TypeSpecValidation code should be refactored to have each logical rule in a separate class, like the following code sample.

Feel free to change the design as you see fit. Don't treat the interfaces below as the final word, they were just a rough sketch.

But I think the design should have some elements of:

  1. A Rule base class / interface
  2. A RuleResult with at least:
    1. Success (boolean)
    2. Output message
      1. Always optional
      2. Always displayed by rule engine on success or fail
    3. Error message
      1. If success, must be empty
      2. If fail, must be non-null and displayed by rule engine
  3. An ordered list of Rule instances that will be executed by the rule engine
interface Rule {
  readonly name: string;
  readonly description: string;
  execute(folder: string): Promise<RuleResult>;
}

interface RuleResult {
  readonly success: boolean;
  readonly output?: string;
  readonly error?: string;
}

class NpmPrefixRule implements Rule {
  readonly name = "NpmPrefix";
  readonly description = "Verify spec is using root level package.json";

  async execute(folder: string): Promise<RuleResult> {
    let expected_npm_prefix = process.cwd();
    const actual_npm_prefix = (await runCmd(`npm prefix`, folder)).trim();

    let success = true;
    let output =
      "Expected npm prefix: " + expected_npm_prefix + "\n" +
      "Actual npm prefix: " + actual_npm_prefix;
    let error: string;

    if (expected_npm_prefix !== actual_npm_prefix) {
      success = false;
      error = "TypeSpec folders MUST NOT contain a package.json, and instead MUST rely on the package.json at repo root"
    }

    return {
      success: success,
      output: output,
      error: error
    }
  }
}

class CompileRule implements Rule {
  readonly name = "Compile";
  readonly description = "Compile TypeSpec"

  async execute(folder: string): Promise<RuleResult> {
    let success = true;
    let output = "";
    let error: string;

    if (await checkFileExists(path.join(folder, "main.tsp"))) {
      output += await runCmd(`npx --no tsp compile . --warn-as-error`, folder);
      // TODO: If command returns nonzero exit, set success=false and append to error
    }
    if (await checkFileExists(path.join(folder, "client.tsp"))) {
      output += await runCmd(`npx --no tsp compile client.tsp --no-emit --warn-as-error`, folder);
      // TODO: Set success=false if command returns nonzero exit
    }
    
    return {
      success: success,
      output: output,
      error: error,
    }
  }
}

class GitDiffRule implements Rule {
  readonly name = "GitDiff";
  readonly description = "Checks if previous rules resulted in a git diff"

  async execute(folder: string): Promise<RuleResult> {
    const git = simpleGit();
    let gitStatusIsClean = await (await git.status(["--porcelain"])).isClean();

    let success = true;
    let error: string;

    if (!gitStatusIsClean) {
      success = false;
      error += await git.status();
      error += await git.diff();
    }
    
    return {
      success: success,
      error: error
    }
  }
}

async function main(folder: string) {
  let rules = [
    new NpmPrefixRule(),
    new CompileRule(),
    new GitDiffRule(),
  ];

  let success = true;
  for (let i = 0; i < rules.length; i++) {
    const rule = rules[i];
    console.log("Executing rule: " + rule.name);
    const result = await rule.execute(folder);
    console.log(result.output);
    if (!result.success) {
      success = false;
    }
  }

  if (!success) {
    exit(1);
  }
}
@mikeharder mikeharder changed the title [TypeSpecValidation] Refactor each rule into a separate "Rule" class [TypeSpec Validation] Refactor each rule into a separate "Rule" class Jul 26, 2023
@mikeharder mikeharder assigned mikeharder and unassigned ckairen Aug 2, 2023
@mikeharder mikeharder removed their assignment Aug 10, 2023
@konrad-jamrozik konrad-jamrozik added the Spec PR Tools Tooling that runs in azure-rest-api-specs repo. label Nov 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Spec PR Tools Tooling that runs in azure-rest-api-specs repo.
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

3 participants