Tool and armor durability allows for items with 0 durability, and only breaks after the next use. This doesn't make much sense: a tool with 100 durability should have 100 uses left, but it actually has 101. Likewise, a tool with its max damage on it should be broken, as the next use would be too much damage and so it shouldn't be able to be used again.
In other words: tools/armor should break at 0 durability, not at -1 durability.
The fix is very simple. Using the Forge for 1.12.1 names, in the ItemStack#attemptDamageItem() method, it currently returns this:
return getItemDamage() > getMaxDamage();
Instead, it should return this:
return getItemDamage() >= getMaxDamage();
Just a "greater than" changed to a "greater than or equal to" fixes the problem everywhere.