| # Fuchsia Platform API evolution guidelines | |
| This section contains guidelines for Fuchsia contributors making changes to | |
| Fuchsia Platform APIs. Before you begin, you should be familiarized with the | |
| following concepts: | |
| - [FIDL versioning](/docs/reference/fidl/language/versioning.md) | |
| ## The lifecycle of a platform API {#lifecycle} | |
| Fuchsia platform APIs should follow the lifecycle: | |
| _Added → Deprecated → Removed → Deleted_, as illustrated below: | |
|  | |
| The following sections explain how to manage this lifecycle as an API developer. | |
| ### Adding FIDL APIs {#adding} | |
| Always annotate new FIDL libraries and elements with an | |
| [`@available`](/docs/reference/fidl/language/versioning.md) attribute. Unstable | |
| APIs should be added at the `HEAD` API level. Note that partners using the SDK | |
| _can_ target the `HEAD` API level, but get no API/ABI compatibility guarantees | |
| if they do so. | |
| For example: | |
| ```fidl | |
| @available(added=HEAD) | |
| library fuchsia.examples.docs; | |
| ``` | |
| When APIs are ready to stabilize, you should update them to be `added` at | |
| `NEXT`. For example: | |
| ```fidl | |
| @available(added=NEXT) | |
| library fuchsia.examples.docs; | |
| ``` | |
| `NEXT` is similar to `HEAD`, except API elements available in `NEXT` will be | |
| automatically added to the next published API level. So, if you added our | |
| library to `NEXT` a week before the API freeze for F23, a week later you'd see: | |
| ```fidl | |
| @available(added=23) | |
| library fuchsia.examples.docs; | |
| ``` | |
| After that point, any API elements added to 23 will need to be supported until | |
| all API levels that include those API elements have been retired. | |
| When a FIDL library has more than one `.fidl` file, the library should include a | |
| separate `overview.fidl` file and the `@available` attribute should be written | |
| in that file along with a documentation comment describing the library. See | |
| [the FIDL style guide][fidl-library-style] | |
| for more information. | |
| Every FIDL API in an [SDK category][sdk-category] | |
| is opted into static compatibility testing in CI/CQ. These tests fail when | |
| an API changes in backward incompatible ways. New libraries do not specify an | |
| SDK category, preventing them from being exposed to partners, excluding them | |
| from compatibility tests, and allowing the API to change freely. Once the API is | |
| stable and the library has gone through [API calibration][calibration], specify | |
| the `"partner"` category. See [Promoting an API][sdk-category-promotion] for | |
| more information. | |
| ### Replacing FIDL APIs {#replacing} | |
| Sometimes you need to replace an API with a new definition. To do this at API | |
| level `N`, annotate the old definition with `@available(replaced=N)` and the new | |
| definition with `@available(added=N)`. For example, this is how you would change | |
| the value of a constant at API level 5: | |
| ```fidl | |
| @available(replaced=5) | |
| const MAX_LENGTH uint32 = 16; | |
| @available(added=5) | |
| const MAX_LENGTH uint32 = 32; | |
| ``` | |
| ### Deprecating FIDL APIs {#deprecating} | |
| It is important that the Fuchsia platform provides smooth transitions for | |
| developers relying on the platform APIs. Part of that is providing ample warning | |
| that APIs will be removed in the future. | |
| [Deprecation](/docs/reference/fidl/language/versioning.md#deprecation) is one | |
| way the this is communicated to developers. | |
| You should always deprecate an API at an earlier level than you remove it. When | |
| an end developer targets a deprecated API, they see a warning at build time that | |
| the API is deprecated and they should migrate to an alternative. You should | |
| include a note to help the end developer find an alternative. For example: | |
| ```fidl | |
| protocol Example { | |
| // (Description of the method.) | |
| // | |
| // # Deprecation | |
| // | |
| // (Detailed explanation of why the method is deprecated, the timeline for | |
| // removing it, and what should be used instead.) | |
| @available(deprecated=5, removed=6, note="use Replacement") | |
| Deprecated(); | |
| @available(added=5) | |
| Replacement(); | |
| }; | |
| ``` | |
| There must be at least one API level between an API's deprecation and removal. | |
| For example: | |
| ```fidl | |
| // These are OK. | |
| @available(deprecated=5, removed=6) | |
| @available(deprecated=5, removed=100) | |
| @available(added=5, deprecated=5) | |
| // These will not compile. | |
| @available(deprecated=5, removed=5) | |
| @available(deprecated=5, removed=3) | |
| ``` | |
| ### Removing FIDL APIs {#removing} | |
| Note that you should always [deprecate](#deprecating) an API before removing it. | |
| To remove an API that was added in a stable level, use the `@available` | |
| attribute's `removed` argument. For example, to remove a method at level 18 that | |
| was deprecated at level 12: | |
| ``` | |
| protocol Example { | |
| @available(added=10, deprecated=12, removed=18, note="Use Go() instead") | |
| Run() -> (); | |
| }; | |
| ``` | |
| In this example, an end developer targeting any level between 10 and 17 | |
| (inclusive) would see client bindings for the `Run` method, but a developer | |
| targeting level 18 or greater would not. Developers working on the Fuchsia | |
| platform would see bindings for the `Run` method as long as the platform | |
| supports any level between 10 and 17, since the platform build targets the set | |
| of all supported API levels. | |
| Once the platform drops support for all levels between 10 and 17, you can delete | |
| the `Run` method from the FIDL file if you wish. If you delete it before that | |
| happens, static compatibility tests will fail and special approval from | |
| //sdk/history/OWNERS will be required to submit the change. | |
| To remove an API that was added at an unstable level such as `NEXT` or `HEAD`, | |
| you can simply delete it from the FIDL file. | |
| ## Designing APIs that evolve gracefully {#evolve-gracefully} | |
| This rubric focuses on promoting compatibility with a range of platform | |
| versions. These attributes make compatibility as easy as possible to maintain | |
| and is a subset of the [FIDL API Rubric][fidl-rubric]. | |
| ### Follow the FIDL Style Guide | |
| The [FIDL style guidelines][fidl-style] are used to make FIDL readable and | |
| embody best practices. These are generally best practices, and should be | |
| followed regardless of sdk_category. | |
| ### Use FIDL Versioning annotations | |
| The [FIDL versioning annotations][fidl-versioning] allow libraries, protocols, | |
| and other elements to be associated with specific API levels. All compatibility | |
| reasoning is based on API version. This is how to express a point in the | |
| evolution of an API. | |
| - Only ever modify an API at the `NEXT` or `HEAD` API level. | |
| - Changes should be implemented at `NEXT` only if they're ready to be released | |
| in the next Fuchsia milestone. | |
| - Numbered API levels should not be changed. See | |
| [version_history.json][version-json]. | |
| ### Specify bounds for vector and string | |
| More information: | |
| [FIDL API Rubric - Specify bounds for vector and string][fidl-string] | |
| ### Use enum vs. boolean | |
| Since booleans are binary, the use of enum which can have multiple states is | |
| preferred when making APIs compatibility-friendly. This way if an additional | |
| state is needed, the enum can be extended, whereas the boolean would have to be | |
| replaced with another type. More information: | |
| [FIDL API Rubric - Avoid booleans if more states are possible][fidl-bool]. | |
| ### Use flexible enums and bits | |
| Flexible enums have a default unknown member, so it allows for easy evolution | |
| of the enum. | |
| Only use `strict` `enum` and `bits` types when you are _extremely_ confident | |
| they will never be extended. `strict` `enum` and `bits` types cannot be | |
| extended, and migrating them to `flexible` requires a migration for every field | |
| with the given type. | |
| More information: [FIDL Language - Strict vs. Flexible][fidl-enum] | |
| ### Prefer tables over structs | |
| Both structs and tables represent an object with multiple named fields. The | |
| difference is that structs have a fixed layout in the wire format, which means | |
| they _cannot_ be modified without breaking binary compatibility. By contrast, | |
| tables have a flexible layout in the wire format, which means fields _can_ be | |
| added to a table over time without breaking binary compatibility. | |
| More information: | |
| [FIDL API Rubric - Should I use a struct or table?][fidl-table] | |
| ### Use open protocols with flexible methods and events | |
| In general, all protocols should be `open`, and all methods and events within | |
| those protocols should be `flexible`. | |
| Marking a protocol as open makes it easier to deal with removing methods or | |
| events when different components might have been built at different versions, | |
| such that each component has a different view of which methods and events exist. | |
| Because flexibility for evolving protocols is generally desirable, it is | |
| recommended to choose open for protocols unless there is a reason to choose a | |
| more closed protocol. | |
| One potential exception is for _tear-off protocols_, representing a transaction, | |
| where the only two-way method is a commit operation which must be strict while | |
| other operations on the transaction may evolve. If a protocol is very small, | |
| unlikely to change, and expected to be implemented by clients, you can make it | |
| `closed` and all the methods `strict`. This will spare the client the trouble of | |
| deciding how to handle an "unknown interaction." The cost, however, is that | |
| methods or events can never be added to or removed from such a protocol. If you | |
| decide you _do_ want to add a method or event, you'll need to define a new | |
| tear-off protocol to replace it. | |
| More information: | |
| - [FIDL API Rubric - strict or flexible?][fidl-flexible] | |
| - [FIDL API Rubric - Open, ajar, or closed?][fidl-open] | |
| ### Use the error syntax | |
| The [error syntax][fidl-error-syntax] is used to specify a method will return a | |
| value, or error out and return an int or enum representing the error. | |
| ## Use a custom error enum, not zx.Status | |
| Use a purpose built enum error type when you define and control the domain. For | |
| example, define an enum when the protocol is purpose built, and conveying the | |
| semantics of the error is the only design constraint. | |
| Use a domain-specific enum error type when you are following a well defined | |
| specification (say HTTP error codes), and the enum is meant to be an ergonomic | |
| way to represent the raw value dictated by the specification. | |
| More information: | |
| [FIDL API Rubric - Prefer domain specific enum for errors][fidl-error-enum]. | |
| ### Don't use declarations from other libraries | |
| It's good for a public API to reuse types and compose protocols if they're | |
| semantically equivalent, but it's easy to make mistakes. | |
| [calibration]: /docs/contribute/governance/api_council.md#calibration | |
| [fidl-bool]: /docs/development/api/fidl.md#avoid_booleans_if_more_states_are_possible | |
| [fidl-enum]: /docs/reference/fidl/language/language.md#strict-vs-flexible | |
| [fidl-error-enum]: /docs/development/api/fidl.md#prefer-domain-specific-enum-for-errors | |
| [fidl-error-syntax]: /docs/development/api/fidl.md#error-syntax | |
| [fidl-flexible]: /docs/development/api/fidl.md#strict-flexible-method | |
| [fidl-open]: /docs/development/api/fidl.md#open-ajar-closed | |
| [fidl-table]: /docs/development/api/fidl.md#should-i-use-a-struct-or-a-table | |
| [fidl-rubric]: /docs/development/api/fidl.md | |
| [fidl-style]: /docs/development/languages/fidl/guides/style.md | |
| [fidl-library-style]: /docs/development/languages/fidl/guides/style.md#library-overview | |
| [fidl-string]: /docs/development/api/fidl.md#specify_bounds_for_vector_and_string | |
| [fidl-versioning]: /docs/reference/fidl/language/versioning.md | |
| [sdk-category]: /docs/contribute/sdk/categories.md | |
| [sdk-category-promotion]: /docs/contribute/sdk/README.md#promoting-an-api | |
| [version-json]: https://cs.opensource.google/fuchsia/fuchsia/+/main:/sdk/version_history.json |