-
Notifications
You must be signed in to change notification settings - Fork 2
Creating Diffing Rules
Currently all rules are provided by writing TypeScript/Javascript code. Rules must satisfy the following signature:
type RuleSignature = (
data: Diff<any, any>,
lhs?: OpenAPIV2.Document,
rhs?: OpenAPIV2.Document
) => RuleResult | [RuleResult, string] | undefined;
Where RuleResult
look like:
export enum RuleResult {
/** The rule applies and flags a verified violation. Stop processing other rules for this diff. */
FlaggedViolation = "F",
/** Rule applies and verifies this is not a violation. Stop processing other rules for this diff. */
NoViolation = "N",
}
Rules that implicitly or explicitly return undefined
will continue processing other rules. If all rules have been processed and no rule could classify the diff as a violation or not, it is assumed to be a violation. Rules may also return a tuple with the result and a custom error message, though generally the diff alone should be sufficient. A rule may require the lhs and/or rhs documents to use in their logic, though most rules do not.
The intended flow is:
- Service team runs
tsp-client convert
to convert their Swagger to TypeSpec. - Service team runs
tsp-client compare
and gets its initial set of diffs. - Service team iterates on the TypeSpec to drive the diff down to (ideally) zero by any of the following:
- Fixing the TypeSpec so there is no relevant diff with the TypeSpec-generated Swagger
- Adding rules to the base tool to ignore patterns that are semantically equivalent
- Adding custom rules to either group diffs into more manageable categories or suppress them from the diff
- Once
tsp-client compare
says the two specs are equivalent, have the spec reviewed. - Reviewers must approve any new filtering rules.
The goals of this are to provide tool users a way to quickly create rules (for suppression or grouping) without having to write code using the most common kinds of rule patterns. The expressiveness of the schema is not intended to enable the creation of all kinds of rules, but the key scenarios that are most commonly used. For more complex scenarios, rules written in TypeScript are the way to go.
This is a very common type of "ignore" or "suppress" rule. These would be used to drive down the violations detected by the tool, and these rule additions would be reviewed and approved as needed.
{
"name": "ignoreParameterFooAdded",
"reason": "Ignores when we add a Foo parameter to RHS.",
"result": "noViolation",
"conditions": [
{
"pathIndex": 4,
"equals": "parameters"
},
{
"pathIndex": -1,
"equals": "Foo"
},
{
"kind": "N"
}
]
}
- name: ignoreParameterFooAdded
reason: Ignores when we add a Foo parameter to RHS.
result: noViolation
conditions:
- pathIndex: 4
equals: parameters
- pathIndex: -1
equals: Foo
- kind: 'N'
This is not a suppression but is also a very common use for rules. It allows the tool to group violations and pull them out of the massive heap of "assumed" violations. 100 "assumed" violations may in fact be duplications of 1 issue, so grouping violations (using the --group-violations
flag) in conjunction with these kinds of rules can help users sort and more quickly address issues in their spec.
{
"name": "flagBarChanged",
"reason": "Flags when Bar is the target of a diff.",
"result": "flagged",
"conditions": [
{
"pathIndex": 4,
"equals": "parameters"
},
{
"pathIndex": -1,
"equals": "Bar"
}
]
}
- name: flagBarChanged
reason: Flags when Bar is the target of a diff.
result: flagged
conditions:
- pathIndex: 4
equals: parameters
- pathIndex: -1
equals: Bar
Just an example of some of the OR logic. Usually, everything is treated as AND.
{
"name": "ignoreIrrelevantParameters",
"reason": "Ignores when irrelevant documentation parameters change.",
"result": "noViolation",
"conditions": [
{
"pathIndex": -1,
"equals": ["description", "title", "summary"]
}
]
}
- name: ignoreIrrelevantParameters
reason: Ignores when irrelevant documentation parameters change.
result: noViolation
conditions:
- pathIndex: -1
equals:
- description
- title
- summary
Here, pathIndex uses a slice (-2, -1) to target the last two elements of the path. If either of the last two elements are '$fooboo', then flag a violation (again, useful for grouping). This would trigger if "$fooboo" were the last added (in which case it would be the last path segement) or a property within "$fooboo" were changed (in which case it would be the second to last path segment).
{
"name": "flagAnyOfChanged",
"reason": "Flags when $fooboo is the target of a diff.",
"result": "flagged",
"conditions": [
{
"pathIndex": 4,
"equals": "parameters"
},
{
"pathIndex": [
-2,
-1
],
"equals": "$fooboo"
}
]
}
- name: flagAnyOfChanged
reason: Flags when $fooboo is the target of a diff.
result: flagged
conditions:
- pathIndex: 4
equals: parameters
- pathIndex: [-2, -1]
equals: "$fooboo"