Search code examples
javascriptnpmrustversionsemantic-versioning

How do minor/patch upgrades in exposed dependencies affect SemVer?


The SemVer Specification details what to do when dependencies are changed without updating the public API. What exactly constitutes a change to the "public API", however, is still unclear to me.

NPM Example

Consider a library which takes a peerDependency on "foo": "^1.0.0" and exposes types from this library. If I update my library to instead take a peerDependency on "foo": "^1.0.1", is this considered a breaking change to my API? I can see the following schools of thought:

  • A consumer taking a dependency on "foo": "1.0.0" will be broken by this change, therefore this is an API-breaking change (and would necessitate a major version bump in my library).
  • Most consumers will be consuming foo through some sort of range-based dependency ("foo": "^1.0.0", etc.) and therefore will transparently work with this change. Therefore, this is a non-API-breaking change.

Rust Example

Consider a library which takes a dependency on foo = "1.0.0". If we increase this dependency to foo = "1.0.1", would this be a breaking change to my API? In this situation, I see the following schools of thought, taking into consideration the following quote from the docs.

Multiple versions within the same compatibility range are not allowed and will result in a resolver error if it is constrained to two different versions within a compatibility range.

  • Any consumer taking a dependency such as foo = "=1.0.0" will be broken by this change, as their code will no longer properly build due to different constraints within the same compatibility range, therefore this is an API-breaking change.
  • Most consumers will be consuming foo through a range-based dependency, and therefore the version will be transparently resolved to a new appropriate version, therefore this is a non-API-breaking change.
  • This is only a breaking change if we expose types from foo in our library. foo is only a part of our API if we expose types from it, and therefore simply upgrading our dependency is not enough to result in an "API-breaking change".

This last school of thought seems to mesh best with how I read the spirit of SemVer, though I'm not sure how correctly it applies in this situation. From my perspective, the exact same subset of users will be broken by this change whether or not we expose this type in our public API, and therefore whether we consider this a breaking change or not should not be affected by this fact.


Solution

  • I only answer for Rust.

    There is no definite answer in the docs, but we can infer it based on existing docs.

    The docs say that:

    Minor: adding dependencies

    It is usually safe to add new dependencies, as long as the new dependency does not introduce new requirements that result in a breaking change. For example, adding a new dependency that requires nightly in a project that previously worked on stable is a major change.

    If we consider not building because of a precise version requirement to be a breaking change, this should also be a breaking change, since introducing a dependency may cause downstream crates that specify precise version requirement that is incompatible with our version to no longer build (for example, if we add a new dependency on foo = "1.0.1", and they have a dependency on foo = "=1.0.0"). Since this is not considered a breaking change, we can infer that we should not consider precise versions when evaluating breaking changes, and therefore, updating a dependency to a semver-compatible version is not a breaking change.

    Updating a depenency to a semver-incompatible version is a breaking change only if we expose it through our public API, as it may cause downstream crates that depend both directly and through our crate on it and expect the types to match to no longer compile.