Fix the schema diff for a public API

Review the AI's codeEasy10 minStripe style

Your task

You asked an AI agent to "clean up and tighten types" in your public REST API, and thousands of customers parse its responses. Tightening types is exactly the prompt that makes a model change a field's type or let it be null to please the compiler. Before merging, you diff the response schema from the main branch against the one from the pull request.

An assistant wrote diff_api_contract(baseline_fields, candidate_fields). It is in the editor. It has a bug. Find it and fix it.

Input and output:

  • baseline_fields: the response fields on the main branch. candidate_fields: the response fields in the pull request. Each is written path|type|required, where the last part is required or optional.
  • Return the findings, sorted.

Rules the tests check:

  • Split a descriptor on |. It is malformed unless it has exactly 3 parts, the path and the type are not empty, and the last part is exactly required or optional. A malformed descriptor, on either side, gives malformed:<descriptor> and is otherwise ignored.
  • If a path appears more than once on one side, use its first descriptor and ignore the rest.
  • For each path in baseline_fields:
    • not in candidate_fields: breaking:removed:<path>
    • a different type: breaking:type_changed:<path>
    • required before and optional now: breaking:now_optional:<path>
    • optional before and required now: compatible:now_required:<path>
  • A path can get a type finding and a required or optional finding at the same time.
  • A path in candidate_fields that is not in baseline_fields gives compatible:added:<path>.
  • Sort the findings by plain string comparison.

Examples

diff_api_contract(baseline_fields: list[str], candidate_fields: list[str]) -> list[str]
Example 1
Inputdiff_api_contract(["id|string|required", "email|string|optional"], ["id|string|required", "email|string|optional"])
Output[]
Example 2
Inputdiff_api_contract(["id|string|required", "email|string|required", "legacy|number|optional"], ["id|number|required", "email|string|optional", "added|string|optional"])
Output["breaking:now_optional:email", "breaking:removed:legacy", "breaking:type_changed:id", "compatible:added:added"]