Azure / azure-sdk-for-rust

This repository is for active development of the *unofficial* Azure SDK for Rust. This repository is *not* supported by the Azure SDK team.
MIT License
680 stars 232 forks source link

Crate versions vs features for each api-version #1606

Closed heaths closed 2 weeks ago

heaths commented 5 months ago

All of our other Azure SDK languages except Python (and I hear they are changing) ship a single binary that supports the latest REST API but does support passing a different api-version. Some languages like .NET use an enum that restricts the API versions you can pass to a known set, while others accept it as a string. In either case, neither provide guarantees it'll work, but it's offered more as an "escape hatch" for callers to use a specific API version.

We plan to do the same for Rust, but wanted to collect feedback if there is a strong reason why we should use features instead of shipping separate crates. Under the current design at the time this issue was opened, all supported API versions are made into features with the latest being the default e.g.,

[features]
package_2023_10_01 = []
package_2024_12_preview = []
package_2024_02_01 = []
default_tag = ["package_2024_02_01"]
default = ["default_tag"]
# Other features about reqwest elided as unnecessary for this example

Then in src/lib.rs:

#[cfg(feature = "package_2023_10_01")]
pub mod package_2023_10_01;
#[cfg(feature = "package_2023_12_15_preview")]
pub mod package_2023_12_15_preview;
#[cfg(feature = "package_2024_02_01")]
pub mod package_2024_02_01;
#[cfg(feature = "default_tag")]
pub use package_2024_02_01::*;

We have concerns about the default_tag feature, in that upgrading your crate means you get potentially breaking changes already. We try to mitigate breaking changes, of course, but it may happen not only with generated or hand-written code, but also the service and, thus, the generated code at least.

We are also worried that crates will only get bigger over time, and especially with ARM crates that rev often, that may be a problem with acquisition.

Additionally, what do we do with preview versions? In other languages, support for these only exist in beta packages and we drop support in stable releases (GA). We could do that even with the current plan, but now customers have to deal with both crate versions and "feature versions".

Thus, we don't see much difference with just doing what we do for other languages: the latest crate is generated from and targets the latest API version. The client and models are always in the crate root and if they update the crate version they may need to change some code either way, though we try to mitigate issues.

They could also still support multiple API versions with crate aliases:

[dependencies]
v1 = { package = "azure_security_keyvault", version = "1.0.0" }
v2 = { package = "azure_security_keyvault", version = "1.1.0" }

Then in their module:

use v1;
use v2;

let client1 = v1::SecretClient::new(...);
let client2 = v1::SecretClient::new(...);

The only disadvantage I can see is that we can't share types with crate aliases while we can with features; however, this isn't currently the case anyway because we generate complete copies of clients and models for each API version. I'm not convinced we'd want to share types e.g., models, anyway because it creates issues around properties that could be dropped and offers no benefit for unused properties: even if serialized, our REST API guidelines recommend failing on properties not supported by the requested api-version; though, most services choose to ignore them or even read/write them (not recommended).

heaths commented 2 weeks ago

We decided that crates will have a 1:1 relationship of crate version : latest spec version. Crates can be imported SxS so it shouldn't be a problem. Future user studies or beta feedback could change that.