FIDL compiler error catalog

This document lists all errors emitted by the FIDL compiler, fidlc. Error identifiers in this domain are always rendered with the prefix fi- followed by a four digit code, like fi-0123.

fi-0001: Invalid character

The lexer failed to convert a character into a token at the specified location.

library test.bad.fi0001;

type ßar = struct {
    value uint64;
};

An invalid character should be fixed by substitution or removal.

library test.good.fi0001;

type Foo = struct {
    value uint64;
};

Invalid characters are location dependent. Please refer to the FIDL language specification to determine which characters are permitted in each part of the FIDL syntax.

fi-0002: Unexpected line break

String literals are not allowed to be split across multiple lines:

library test.bad.fi0002;

const BAD_STRING string:1 = "Hello
World";

Instead, use the escape sequence \n to represent a line break:

library test.good.fi0002;

const GOOD_STRING string:11 = "Hello\nWorld";

fi-0003: Invalid escape sequence

The lexer encountered an invalid character at the beginning of an escape sequence.

library test.bad.fi0003;

const UNESCAPED_BACKSLASH string:2 = "\ ";
const BACKSLASH_TYPO string:1 = "\i";
const CODE_POINT_TYPO string:1 = "\Y1F604";

Substitute a valid character to begin the escape sequence, or remove the unintended backslash character.

library test.good.fi0003;

const ESCAPED_BACKSLASH string:2 = "\\ ";
const REMOVED_BACKSLASH string:1 = "i";
const SMALL_CODE_POINT string:3 = "\u{2604}";
const BIG_CODE_POINT string:4 = "\u{01F604}";

Refer to the FIDL grammar specification for valid escape sequences.

fi-0004: Invalid hex digit

Unicode escapes in string literals must not contain invalid hex digits:

library test.bad.fi0004;

const SMILE string = "\u{1G600}";

You must specify a valid Unicode code point in hexadecimal, from 0 to 10FFFF. Each hex digit must be a number from 0 to 9, a lowercase letter from a to f, or an uppercase letter from A to F. In this case, G was a typo for F:

library test.good.fi0004;

const SMILE string = "\u{1F600}";

fi-0005

fi-0006: Expected declaration

This error happens when FIDL expects a declaration and finds something else. This is often caused by a typo. The valid declarations are: type, alias, const, using, protocol, and service.

library test.bad.fi0006;

cosnt SPELLED_CONST_WRONG string:2 = ":("; // Expected a declaraction (such as const).

To fix this error, check for typos in your top-level declarations and ensure you're only using features that are supported by FIDL.

library test.good.fi0006;

const SPELLED_CONST_RIGHT string:2 = ":)";

fi-0007: Unexpected token

This error occurs when an unexpected token is encountered during parsing. Generally speaking, this is the result of a typo:

library test.bad.fi0007;

alias MyType = vector<uint8>:<,256,optional>; // Extra leading comma

The fix to this problem is typically to remove the unexpected token, or in some cases, provide the rest of the missing syntax:

library test.good.fi0007;

alias MyType = vector<uint8>:<256, optional>;

fi-0008: Unexpected token

This error happens whenever the FIDL parser encounters a grammatically invalid token. This can happen many ways--for example a missing = for an enum member, an extra token such as library = what.is.that.equals.doing.there, and so on.

library test.bad.unexpectedtokenofkind;

type Numbers = flexible enum {
    ONE; // FIDL enums don't have a default value.
};

Generally, the fix to this problem will be adding a missing token or removing an extra one.

library test.good.fi0008;

type Numbers = flexible enum {
    ONE = 1;
};

To avoid this error, do a once over on your *.fidl files to make sure they're grammatically correct.

fi-0009: Unexpected identifier

This error usually occurs when an identifier is used in an incorrect position:

using test.bad.fi0009;

Use the correct identifier instead:

library test.good.fi0009;

fi-0010: Invalid identifier

An identifier was found which does not meet the requirements for valid identifiers. FIDL identifiers may contain alphanumerics and underscores (specifically A-Z, a-z, 0-9, and _), and additionally each identifier must begin with a letter and end with either a letter or number.

library test.bad.fi0010a;

// Foo_ is not a valid identifier because it ends with '_'.
type Foo_ = struct {
    value uint64;
};

To fix this, change the identifier to make sure it contains only valid characters, starts with a letter, and ends with a letter or number.

library test.good.fi0010a;

type Foo = struct {
    value uint64;
};

This error may also occur if a multi-part (dotted) identifier is passed to an attribute.

library test.bad.fi0010b;

@foo(bar.baz="Bar", zork="Zoom")
type Empty = struct{};

To fix this, change to use only single-part identifiers in attributes.

library test.good.fi0010b;

@foo(bar="Bar", zork="Zoom")
type Empty = struct {};

fi-0011: Invalid library name component

Library names must only contain letters and numbers (A-Z, a-z, and 0-9), and must start with a letter.

library test.bad.fi0011.name_with_underscores;

To fix this, ensure that all library name components meet the requirements.

library test.good.fi0011.namewithoutunderscores;

fi-0012: Invalid type layout class

Type declarations must specify a layout known to FIDL:

library test.bad.fi00012;

type Foo = invalid {};

The valid layouts are bits, enum, struct, table, and union:

library test.good.fi0012;

type Empty = struct {};

A layout is a parameterizable description of a FIDL type. It refers to a family of would-be types which can receive further arguments to specify their shape. For example, a struct is a kind of layout which becomes a concrete type when it has specific members defined, while an array is a layout that becomes concrete when given a type to be repeated sequentially a specified number of times.

Layouts are all built into the FIDL language - there is no means by which users can specify their own layouts, or create their own generic type templates.

fi-0013: Invalid wrapped type

This error occurs when the value passed to an enum or bits declaration isn't an identifier for a type, such as when you instead provide a string value as the "backing type":

library test.bad.fi0013;

type TypeDecl = enum : "int32" {
    FOO = 1;
    BAR = 2;
};

To fix this error, make sure the backing type for the enum or bits is a type identifier.

library test.good.fi0013;

type TypeDecl = enum : int32 {
    FOO = 1;
    BAR = 2;
};

fi-0014: Attribute with empty parentheses

This error occurs if an attribute has parentheses but no arguments.

library test.bad.fi0014;

@discoverable()
protocol MyProtocol {};

To fix it, remove the parentheses from attributes with no arguments, or supply arguments if arguments were intended.

library test.good.fi0014;

@discoverable
protocol MyProtocol {};

FIDL disallows empty argument lists on attributes mostly as a stylistic choice.

fi-0015: Attribute args must all be named

For clarity, when an attribute has more than one argument, all of the attribute's arguments must be explicitly named.

This error occurs when an attribute has more than one argument but does not explicitly provide names for the arguments.

library test.bad.fi0015;

@foo("abc", "def")
type MyStruct = struct {};

To fix it, provides names for all of the arguments using name=value syntax.

library test.good.fi0015;

@foo(bar="abc", baz="def")
type MyStruct = struct {};

fi-0016: Missing ordinal before member

This error occurs when a fields in a union or table is missing an ordinal.

library test.bad.fi0016a;

type Foo = table {
    x int64;
};
library test.bad.fi0016b;

type Bar = union {
    foo int64;
    bar vector<uint32>:10;
};

To fix this error, explicitly specify the ordinals for the table or union:

library test.good.fi0016;

type Foo = table {
    1: x int64;
};

type Bar = union {
    1: foo int64;
    2: bar vector<uint32>:10;
};

Unlike structs, tables and unions are designed to allow backwards-compatible changes to be made to their contents. In order to enable this, a consistent value, the ordinal, is needed to identify table fields or union variants. To avoid confusion and make it harder to accidentally change ordinals when changing a table or union, ordinals must always be specified explicitly.

fi-0017: Ordinal out of bound

Ordinals for tables and Unions must be valid unsigned 32 bit integers. Negative ordinals or ordinals greater than 4,294,967,295 will cause this error.

library test.bad.fi0017a;

type Foo = table {
  -1: foo string;
};
library test.bad.fi0017b;

type Bar = union {
  -1: foo string;
};

To fix this error, ensure that all ordinals are in the allowed range.

library test.good.fi0017;

type Foo = table {
    1: foo string;
};

type Bar = union {
    1: foo string;
};

fi-0018: Ordinals must start at 1

Neither table nor union member ordinal values are allowed to be 0:

library test.bad.fi0018;

type Foo = strict union {
    0: foo uint32;
    1: bar uint64;
};

Instead, numbering should start from 1:

library test.good.fi0018;

type Foo = strict union {
    1: foo uint32;
    2: bar uint64;
};

fi-0019: Strict bits, enum, or union cannot be empty

A strict bits, enum, or union is not allowed to have zero members:

library test.bad.fi0019;

type Numbers = strict enum {};

Instead, there must be at least one member:

library test.good.fi0019a;

type Numbers = flexible enum {};

Alternatively, you can mark the declaration flexible instead of strict:

library test.good.fi0019b;

type Numbers = strict enum {
    ONE = 1;
};

An empty bits, enum, or union carries no information, so it should not normally be used in an API. However, flexible data types are designed for evolution, so it makes sense to define a flexible bits or enum that starts out empty, with the expectation of adding members later on. You should always think carefully about whether to use strict or flexible when defining a new data type.

fi-0020: Invalid protocol member

This error occurs when an item in a protocol is not recognized as a valid protocol member such as when something in a protocol is not a protocol composition, one-way method, two-way method, or event.

library test.bad.fi0020;

protocol Example {
    NotAMethodOrCompose;
};

To fix this error, remove the invalid items or convert them to the correct syntax for the type of protocol item they were intended to be.

library test.good.fi0020;

protocol Example {
    AMethod();
};

fi-0021

fi-0022: Cannot attach attribute to identifier

This error occurs when an attribute is placed on the type of a declaration when that type is an identifier type. For example, placing an attribute after field name but before the field's type in a struct declaration associates the attribute with the type of the field rather than with the field itself. If the type of the field is a preexisting type being referenced by name, additional attributes cannot be applied to it.

library test.bad.fi0022;

type Foo = struct {
    // uint32 is an existing type, extra attributes cannot be added to it just
    // for this field.
    data @foo uint32;
};

If the intent was to apply an attribute to the field, the attribute should be moved before the field name.

Attributes can be applied to types where they are declared. This means that if the type of a struct field or other similar declaration is an anonymous type rather than an identifier type, attributes can be applied to the type.

library test.good.fi0022;

type Foo = struct {
    // The foo attribute is associated with the data1 field, not the uint32
    // type.
    @foo
    data1 uint32;
    // The type of data2 is a newly declared anonymous structure, so that new
    // type can have an attribute applied to it.
    data2 @foo struct {};
};

fi-0023: Redundant attribute placement

Attributes applied to the declaration of a named type apply to the resulting type the same as attributes applied directly to the type's layout.

This error occurs attributes are placed on both a type declaration and that declaration's layout.

library test.bad.fi0023;

@foo
type Foo = @bar struct {};

To fix this error, either move all attributes to the type declaration or move all attributes to the layout.

library test.good.fi0023;

@foo
@bar
type Foo = struct {};
type Bar = @foo @bar struct {};

FIDL includes this error to avoid confusion which might be caused by having attributes on both the declaration and layout of a single type.

fi-0024: Doc comment on method parameter list

Method parameter lists cannot carry doc comments:

library test.bad.fi0024;

protocol Example {
    Method(/// This is a one-way method.
            struct {
        b bool;
    });
};

For the time being, place doc comments on the method itself:

library test.good.fi0024;

protocol Example {
    /// This is a one-way method.
    Method(struct {
        b bool;
    });
};

This error will no longer exist once this bug is resolved. The error a holdover from the migration to describe method payloads using FIDL types, rather than parameter lists.

fi-0025: Imports group must be at top of file

Except for the library declaration at the top of the file, there cannot be any other declarations before the using imports for the file, should they exist:

library test.bad.fi0025;

alias i16 = int16;
using dependent;

type UsesDependent = struct {
    field dependent.Something;
};

To resolve this error, place all of your using imports in a block directly after the library declaration:

library test.good.fi0025;

using dependent;

alias i16 = int16;
type UsesDependent = struct {
    field dependent.Something;
};

This rule reflects a primarily aesthetic decision by the FIDL team that dependencies are easier to read when they are well-grouped and easily located.

fi-0026: Comment inside doc comment block

Comments should not be placed inside of a doc comment block:

library test.bad.fi0026;

/// start
// middle
/// end
type Empty = struct {};

Instead, comments should be placed before or after the doc comment block:

library test.good.fi0026;

// some comments above,
// maybe about the doc comment
/// A
/// multiline
/// comment!
// another comment, maybe about the struct
type Empty = struct {};

Generally, comments immediately preceding the doc comment block are the best place for comments about the doc comment itself.

fi-0027: Blank lines in doc comment block

There should be no blank lines in a doc comment block:

library test.bad.fi0027;

/// start

/// end
type Empty = struct {};

Instead, blank lines should only be placed before or after the doc comment block:

library test.good.fi0027a;

/// A doc comment
type Empty = struct {};

Alternatively, consider omitting the blank lines altogether:

library test.good.fi0027b;

/// A doc comment
type Empty = struct {};

fi-0028: Doc comment must be followed by a declaration

Doc comments are never allowed to be free-floating, like regular comments:

library test.bad.fi0028;

type Empty = struct {};
/// bad

Doc comments must directly precede FIDL declarations in all circumstances:

library test.good.fi0028a;

/// A doc comment
type Empty = struct {};

FIDL "lowers" doc comments to @doc attributes during compilation. In fact, any comment can be written directly in such a manner if so desired:

library test.good.fi0028b;

@doc("An attribute doc comment")
type Empty = struct {};

Standalone doc comments are non-compilable from a technical perspective, but are also confusing semantically: what does it mean to "document" nothing? Unlike regular comments, doc comments get processed into structured documentation, and thus must be clear about which FIDL construct they are attached to.

fi-0029: Resource definition must have at least one property

Resource definitions with no properties specified are prohibited:

library test.bad.resourcedefinitionnoproperties;

resource_definition SomeResource : uint32 {
  properties {};
};

Please specify at least one property:

library test.good.fi0029;

resource_definition SomeResource : uint32 {
    properties {
        subtype strict enum : uint32 {
            NONE = 0;
        };
    };
};

This is an error related to FIDL's internal implementation, and thus should only ever be surfaced to developers working on FIDL's core libraries. End users should never see this error.

The resource_definition declaration it refers to is FIDL's internal means of defining resources like handles, and is likely to change in the future as part of the handle generalization effort.

fi-0030: Invalid modifier

Each FIDL modifier has a specific set of declarations in which it can be used. Using the modifier in a prohibited declaration is not allowed:

library test.bad.fi0030;

type MyStruct = strict struct {
    i int32;
};

The best course of action is to remove the offending modifier:

library test.good.fi0030;

type MyStruct = struct {
    i int32;
};

fi-0031: Only bits and enum can have subtype

Not every FIDL layout can carry a subtype:

library test.bad.fi0031;

type Foo = flexible union : uint32 {};

Only bits and enum layouts are defined over an underlying type.

library test.good.fi0031;

type Foo = flexible enum : uint32 {};

The bits and enum layouts are somewhat unique, in that they are just constrained subtypings of the integral FIDL primitives. Because of this, it makes sense for them to specify an underlying type which acts as this subtype. Conversely, struct, table, and union layouts can be arbitrarily large and can contain many members, therefore a global, layout-wide subtype does not make sense.

fi-0032: Duplicate modifiers disallowed

Specifying the same modifier in a single declaration is prohibited:

library test.bad.fi0032;

type MyUnion = strict resource strict union {
    1: foo bool;
};

Remove the duplicated modifier:

library test.good.fi0032;

type MyUnion = resource strict union {
    1: foo bool;
};

fi-0033: Conflicting modifiers

Certain modifiers are mutually exclusive of one another and cannot both modify the same declaration:

library test.bad.conflictingmodifiers;

type StrictFlexibleFoo = strict flexible union {
    1: b bool;
};

type FlexibleStrictBar = flexible strict union {
    1: b bool;
};

Only one of the strict or flexible modifiers may be used on a single declaration at a time:

library test.good.fi0033;

type FlexibleFoo = flexible union {
    1: i int32;
};

type StrictBar = strict union {
    1: i int32;
};

At this time, only the strict and flexible modifiers are mutually exclusive in this manner. The resource modifier has no reciprocal modifier, and thus has no such restrictions applied to it.

fi-0034: Name collision

Two declarations cannot have the same name:

library test.bad.fi0034;

const COLOR string = "red";
const COLOR string = "blue";

Instead, give each declaration a unique name:

library test.good.fi0034b;

const COLOR string = "red";
const OTHER_COLOR string = "blue";

Alternatively, remove one of the declarations if it was added by mistake:

library test.good.fi0034a;

const COLOR string = "red";

For more information on choosing names, see the FIDL style guide.

fi-0035: Canonical name collision

Two declarations cannot have the same canonical name:

library test.bad.fi0035;

const COLOR string = "red";

protocol Color {};

Even though COLOR and Color look different, they are both represented by the canonical name color. You get the canonical name by converting the original name to snake_case.

To fix the error, give each declaration a name that is unique after canonicalization:

library test.good.fi0035;

const COLOR string = "red";

protocol ColorMixer {};

Following the FIDL style guide's naming guidelines will minimize your chances of running into this error. Canonical name collisions will never happen between declarations that use the same casing style, and they will rarely happen between ones that use different styles because of other requirements (e.g. protocol names should usually be noun phrases ending in -er).

FIDL enforces this rule because bindings generators transform names to the idiomatic naming style for the target language. By ensuring unique canonical names, we guarantee that bindings can do this without producing name collisions. See RFC-0040: Identifier uniqueness for more details.

fi-0036: Name overlap

Declarations with the same name cannot have overlapping availabilities:

@available(added=1)
library test.bad.fi0036;

type Color = strict enum {
    RED = 1;
};

@available(added=2)
type Color = flexible enum {
    RED = 1;
};

Instead, use the @available attribute to make sure only one of the declarations is present at any given version:

@available(added=1)
library test.good.fi0036;

@available(replaced=2)
type Color = strict enum {
    RED = 1;
};

@available(added=2)
type Color = flexible enum {
    RED = 1;
};

Alternatively, rename or remove one of the declarations as shown in fi-0034.

See FIDL versioning to learn more about versioning.

fi-0037: Canonical name overlap

Declarations with the same canonical name cannot have overlapping availabilities:

@available(added=1)
library test.bad.fi0037;

const COLOR string = "red";

@available(added=2)
protocol Color {};

Even though COLOR and Color look different, they are both represented by the canonical name color. You get the canonical name by converting the original name to snake_case.

To fix the error, give each declaration a name that is unique after canonicalization.

@available(added=1)
library test.good.fi0037;

const COLOR string = "red";

@available(added=2)
protocol ColorMixer {};

Alternatively, change one of the declarations availabilities as shown in fi-0036, or remove the declaration.

See fi-0035 for more details on why FIDL requires declarations to have unique canonical names.

fi-0038: Name conflicts with import

A declaration cannot have the same name as a library imported with using:

library dependency;

const VALUE uint32 = 1;
library test.bad.fi0038b;

using dependency;

type dependency = struct {};

// Without this, we'd get fi-0178 instead.
const USE_VALUE uint32 = dependency.VALUE;

Instead, import the library under a different name with the using ... as syntax:

library test.good.fi0038b;

using dependency as dep;

type dependency = struct {};

const USE_VALUE uint32 = dep.VALUE;

Alternatively, rename the declaration to avoid the conflict:

library test.good.fi0038c;

using dependency;

type OtherName = struct {};

const USE_VALUE uint32 = dependency.VALUE;

You can avoid this problem by using multiple components in library names. For example, FIDL libraries in the Fuchsia SDK start with fuchsia., so they have at least two components and cannot conflict with declaration names.

This error exists to prevent ambiguities. For example, if dependency were an enum with a member called VALUE, it would be ambiguous whether dependency.VALUE referred to that enum member or to the const declared in the imported library.

fi-0039: Canonical name conflicts with import

A declaration cannot have the same canonical name as a library import with using:

library dependency;

const VALUE uint32 = 1;
library test.bad.fi0039b;

using dependency;

type Dependency = struct {};

// Without this, we'd get fi-0178 instead.
const USE_VALUE uint32 = dependency.VALUE;

Even though dependency and Dependency look different, they are both represented by the canonical name dependency. You get the canonical name by converting the original name to snake_case.

To fix the error, import the library under a different name with the using ... as syntax:

library test.good.fi0039b;

using dependency as dep;

type Dependency = struct {};

const USE_VALUE uint32 = dep.VALUE;

Alternatively, rename the declaration to avoid the conflict:

library test.good.fi0039c;

using dependency;

type OtherName = struct {};

const USE_VALUE uint32 = dependency.VALUE;

See fi-0038 to learn why this error exists and how to avoid it.

See fi-0035 for more details on why FIDL requires declarations to have unique canonical names.

fi-0040: Files disagree on library name

Libraries can be composed of multiple files, but each file must have the same name:

library test.bad.fi0040a;
library test.bad.fi0040b;

Ensure that all files used by a library share the same name:

library test.good.fi0040;
library test.good.fi0040;

An encouraged convention for multi-file libraries is to create an otherwise empty overview.fidl file to serve as the main "entry point" into the library. The overview.fidl file is also the appropriate place to put library-scoped @available platform specifications.

fi-0041: Multiple libraries with same name

Each library passed to fidlc must have a unique name:

library test.bad.fi0041;
library test.bad.fi0041;

Ensure that all libraries have unique names:

library test.good.fi0041a;
library test.good.fi0041b;

This error is often caused by arguments being supplied to fidlc in an incorrect manner. The constituent files that make up each library necessary for compilation (that is, the library being compiled and all of its transitive dependencies) must be supplied as a single space-separated list of files passed via the --files argument, with one such flag per library. A common mistake is to try to pass the file for all libraries in a single --files list.

fi-0042: Duplicate library import

Dependencies cannot be imported multiple times:

library test.bad.fi0042a;

type Bar = struct {};
library test.bad.fi0042b;

using test.bad.fi0042a;
using test.bad.fi0042a; // duplicated
type Foo = struct {
    bar test.bad.fi0042a.Bar;
};

Ensure that each dependency is only imported once:

library test.good.fi0042a;

type Bar = struct {};
library test.good.fi0042b;

using test.good.fi0042a;
type Foo = struct {
    bar test.good.fi0042a.Bar;
};

It is worth noting that FIDL does not support importing different versions of the same library. The @available version is resolved for the entire fidlc compilation via the --available flag, meaning that both the library being compiled and all of its dependencies must share the same version for any given compilation run.

fi-0043: Conflicting library imports

Imported libraries are prohibited from being aliased in such a way that they conflict with the non-aliased names of other imported libraries:

library test.bad.fi0043a;

type Bar = struct {};

// This library has a one component name to demonstrate the error.
library fi0043b;

type Baz = struct {};
library test.bad.fi0043c;

using test.bad.fi0043a as fi0043b; // conflict
using fi0043b;

type Foo = struct {
    a fi0043b.Bar;
    b fi0043b.Baz;
};

Choose a different alias to resolve the name conflict:

library test.good.fi0043a;

type Bar = struct {};
library fi0043b;

type Baz = struct {};
library test.good.fi0043c;

using test.good.fi0043a as dep;
using fi0043b;

type Foo = struct {
    a dep.Bar;
    b fi0043b.Baz;
};

fi-0044: Conflicting library import aliases

Imported libraries are prohibited from being aliased in such a way that they conflict with the aliases assigned to other imported libraries:

library test.bad.fi0044a;

type Bar = struct {};
library test.bad.fi0044b;

type Baz = struct {};
library test.bad.fi0044c;

using test.bad.fi0044a as dep;
using test.bad.fi0044b as dep; // conflict
type Foo = struct {
    a dep.Bar;
    b dep.Baz;
};

Choose non-conflicting aliases to resolve the name conflict:

library test.good.fi0044a;

type Bar = struct {};
library test.good.fi0044b;

type Baz = struct {};
library test.good.fi0044c;

using test.good.fi0044a as dep1;
using test.good.fi0044b as dep2;
type Foo = struct {
    a dep1.Bar;
    b dep2.Baz;
};

fi-0045: Attributes disallowed on using declarations

Attributes cannot be attached to using declarations:

library test.bad.fi0045a;

type Bar = struct {};
library test.bad.fi0045b;

/// not allowed
@also_not_allowed
using test.bad.fi0045a;

type Foo = struct {
    bar test.bad.fi0045a.Bar;
};

Remove the attribute to resolve the error:

library test.good.fi0045a;

type Bar = struct {};
library test.good.fi0045b;

using test.good.fi0045a;

type Foo = struct {
    bar test.good.fi0045a.Bar;
};

This restriction applies to /// ... doc comments as well, as those are merely syntactic sugar for the @doc("...") attribute.

fi-0046: Unknown library

In most cases, this problem is due to the dependency being misspelled or not provided by the build system. If the dependency in question is intentionally unused, the relevant using line must be removed:

library test.bad.fi0046;

using dependent; // unknown using.

type Foo = struct {
    dep dependent.Bar;
};

Make sure all imports are added as dependencies to the library using the build system.

library test.good.fi0046;

type Foo = struct {
    dep int64;
};

fi-0047: Protocol composed multiple times

A protocol cannot compose another protocol more than once:

library test.bad.fi0047;

protocol Parent {
    Method();
};

protocol Child {
    compose Parent;
    compose Parent;
};

Remove redundant compose clauses:

library test.good.fi0047;

protocol Parent {
    Method();
};

protocol Child {
    compose Parent;
};

fi-0048: Optional table member

Table members types cannot be optional:

library test.bad.fi0048;

type Foo = table {
    // Strings can be optional in general, but not in table member position.
    1: t string:optional;
};

Remove the optional constraint from all members:

library test.good.fi0048;

type Foo = table {
    1: t string;
};

Table members are always optional, so specifying this fact on the member's underlying type is redundant.

Table members are always optional because, on the wire, each table member is represented as an entry in a vector. This vector is always represents all known fields on the table, so every omitted table member is represented as a null envelope - exactly equivalent to the representation of an omitted optional type.

fi-0049: Optional union member

Union members cannot be optional:

library test.bad.fi0049;

type Foo = strict union {
    // Strings can be optional in general, but not in unions.
    1: bar string:optional;
};

Remove the optional constraint:

library test.good.fi0049;

type Foo = strict union {
    1: bar string;
};

FIDL does not allow union members to be optional because this can result in many ways of expressing the same value. For example, a union with three optional members would have 6 states (2 per member). Instead, this should be modeled with a fourth member whose type is struct {}, or by making the overall union optional with Foo:optional.

fi-0050: Deprecated struct default syntax prohibited

Previously, FIDL allowed for default values to be set on struct members:

library test.bad.fi0050;

type MyStruct = struct {
    field int64 = 20;
};

As of RFC-0160: Remove support for FIDL struct defaults, this behavior is disallowed:

library test.good.fi0050;

type MyStruct = struct {
    field int64;
};

Default values for struct members are no longer allowed. Users should set such defaults in application logic instead.

A small number of legacy users of this syntax are allowed to continue using it behind an allowlist built into the compiler, but no new exceptions are being made to add to this list. As soon as these users are migrated off, this feature will be permanently removed from FIDL.

fi-0051: Unknown dependent library

This error occurs when you use a symbol that's from an unknown library.

library test.bad.fi0051;

type Company = table {
    1: employees vector<unknown.dependent.library.Person>;
    2: name string;
};

To fix this, import the missing dependent library with a using declaration.

library known.dependent.library;

type Person = table {
    1: age uint8;
    2: name string;
};
library test.good.fi0051;
using known.dependent.library;

type Company = table {
    1: employees vector<known.dependent.library.Person>;
    2: name string;
};

This error commonly occurs when the fidlc command line invocation is improperly formed. If you are confident that the unknown library exists and should be resolvable, make sure you are passing the dependent library's files correctly via a space-separated list passed to the --files flag.

fi-0052: Name not found

This error occurs when you use a name that the FIDL compiler cannot find.

library test.bad.fi0052;

protocol Parser {
    Tokenize() -> (struct {
        tokens vector<string>;
    }) error ParsingError; // ParsingError doesn't exist.
};

To fix this, either remove the name that's not found:

library test.good.fi0052a;

protocol Parser {
    Tokenize() -> (struct {
        tokens vector<string>;
    });
};

or define the name that's not found:

library test.good.fi0052b;

type ParsingError = flexible enum {
    UNEXPECTED_EOF = 0;
};

protocol Parser {
    Tokenize() -> (struct {
        tokens vector<string>;
    }) error ParsingError;
};

fi-0053: Cannot refer to member

This error occurs when you refer to a member that's not a bits or enum entry.

library test.bad.fi0053a;

type Person = struct {
    name string;
    birthday struct {
        year uint16;
        month uint8;
        day uint8;
    };
};

const JOHNS_NAME Person.name = "John Johnson"; // Cannot refer to member of struct 'Person'.
library test.bad.fi0053b;

type Person = struct {
    name string;
    birthday struct {
        year uint16;
        month uint8;
        day uint8;
    };
};

type Cat = struct {
    name string;
    age Person.birthday; // Cannot refer to member of struct 'Person'.
};

To fix this error, either change to a named type:

library test.good.fi0053a;

type Person = struct {
    name string;
    birthday struct {
        year uint16;
        month uint8;
        day uint8;
    };
};

const JOHNS_NAME string = "John Johnson";

or extract the member's type:

library test.good.fi0053b;

type Date = struct {
    year uint16;
    month uint8;
    day uint8;
};

type Person = struct {
    name string;
    birthday Date;
};

type Cat = struct {
    name string;
    age Date;
};

fi-0054: Invalid bits/enum member

This error occurs when an enum or bits member is referenced without being previously defined.

library test.bad.fi0054;

type Enum = enum {
    foo_bar = 1;
};

const EXAMPLE Enum = Enum.FOO_BAR;

To avoid this error, confirm that you have previously declared a value for the referenced member value. These values are case sensitive.

library test.good.fi0054;

type Enum = enum {
    foo_bar = 1;
};

const EXAMPLE Enum = Enum.foo_bar;

fi-0055: Invalid reference to deprecated

This error occurs when you use a reference to a type or const a with an incompatible @available attribute. This typically happens when using deprecated types or consts from later versions.

@available(added=1)
library test.bad.fi0055;

@available(added=1, deprecated=2, note="use Color instead")
alias RGB = array<uint8, 3>;

@available(added=2)
type Color = struct {
    r uint8;
    g uint8;
    b uint8;
    a uint8;
};

@available(added=3)
type Config = table {
    // RGB is deprecated in version 2.
    1: color RGB;
};

To fix this error, use a non-deprecated type or const:

@available(added=1)
library test.good.fi0055;

@available(added=1, deprecated=2, note="use Color instead")
alias RGB = array<uint8, 3>;

@available(added=2)
type Color = struct {
    r uint8;
    g uint8;
    b uint8;
    a uint8;
};

@available(added=3)
type Config = table {
    // Using a non-deprecated type.
    1: color Color;
};

fi-0056: Invalid reference to deprecated other platform

This error occurs when you use a reference to a type or const from another platform with an incompatible @available attribute. This typically happens when using deprecated types or consts from later versions.

@available(platform="foo", added=1)
library test.bad.fi0056a;

@available(added=1, deprecated=2, note="use Color instead")
alias RGB = array<uint8, 3>;

@available(added=2)
type Color = struct {
    r uint8;
    g uint8;
    b uint8;
    a uint8;
};
@available(platform="bar", added=2)
library test.bad.fi0056b;

using test.bad.fi0056a;

@available(added=3)
type Config = table {
    // RGB is deprecated in version 2.
    1: color test.bad.fi0056a.RGB;
};

To fix this error, use a non-deprecated type or const:

@available(platform="foo", added=1)
library test.good.fi0056a;

@available(added=1, deprecated=2, note="use Color instead")
alias RGB = array<uint8, 3>;

@available(added=2)
type Color = struct {
    r uint8;
    g uint8;
    b uint8;
    a uint8;
};
@available(platform="bar", added=2)
library test.good.fi0056b;

using test.good.fi0056a;

@available(added=2)
type Config = table {
    // Change to use a non-deprecated type.
    1: color test.good.fi0056a.Color;
};

fi-0057: Includes cycle

There are a number of situations that could cause this problem to occur, but all of them basically boil down to a FIDL declaration referring to itself in an unresolvable way. The simplest form of this error is when a type or protocol refers directly to itself in its own definition:

library test.bad.fi0057c;

type MySelf = struct {
    me MySelf;
};

More complex failure cases are possible when a type or protocol transitively refers to itself via at least one level of indirection:

library test.bad.fi0057a;

type Yin = struct {
    yang Yang;
};

type Yang = struct {
    yin Yin;
};
library test.bad.fi0057b;

protocol Yin {
    compose Yang;
};

protocol Yang {
    compose Yin;
};

This error can be resolved by adding an envelope (aka, optionality) somewhere in the inclusion cycle, as this allows the cycle to be "broken" at encode/decode time:

library test.good.fi0057;

type MySelf = struct {
    me box<MySelf>;
};
library test.bad.fi0057d;

type MySelf = table {
    1: me MySelf;
};

Recursive types that are unbroken by envelopes are disallowed because they would be impossible to encode. In the first example above, encoding MySelf would require first encoding an instance of MySelf, which would in turn require encoding an instance of MySelf, ad inifitum. A solution to this problem is to add a "break" in this chain via optionality, where one may choose to either encode another nested instance of MySelf, or otherwise encode a null envelope with no further data.

fi-0058: Reference to compiler generated payload name

Anonymous method payloads have their names automatically generated by the FIDL compiler, so that users of generated backend code may refer to the types they represent as necessary. Referring to these types in *.fidl files themselves, however, is forbidden:

library test.bad.fi0058;

protocol MyProtocol {
    strict MyInfallible(struct {
        in uint8;
    }) -> (struct {
        out int8;
    });
    strict MyFallible(struct {
        in uint8;
    }) -> (struct {
        out int8;
    }) error flexible enum {};
    strict -> MyEvent(struct {
        out int8;
    });
};

type MyAnonymousReferences = struct {
    a MyProtocolMyInfallibleRequest;
    b MyProtocolMyInfallibleResponse;
    c MyProtocolMyFallibleRequest;
    d MyProtocol_MyFallible_Result;
    e MyProtocol_MyFallible_Response;
    f MyProtocol_MyFallible_Error;
    g MyProtocolMyEventRequest;
};

If you wish to refer to the payload type directly, you should extract the payload type into its own named type declaration instead:

library test.good.fi0058;

type MyRequest = struct {
    in uint8;
};
type MyResponse = struct {
    out int8;
};
type MyError = flexible enum {};

protocol MyProtocol {
    strict MyInfallible(MyRequest) -> (MyResponse);
    strict MyFallible(MyRequest) -> (MyResponse) error MyError;
    strict -> MyEvent(MyResponse);
};

type MyAnonymousReferences = struct {
    a MyRequest;
    b MyResponse;
    c MyRequest;
    // There is no way to explicitly name the error result union.
    // d MyProtocol_MyFallible_Result;
    e MyResponse;
    f MyError;
    g MyResponse;
};

All FIDL methods and events reserve the [PROTOCOL_NAME][METHOD_NAME]Request name for their anonymous request payloads. Strict, infallible two-way methods additionally reserve [PROTOCOL_NAME][METHOD_NAME]Response. Two-way methods that are flexible or fallible instead reserve:

  • [PROTOCOL_NAME]_[METHOD_NAME]_Result
  • [PROTOCOL_NAME]_[METHOD_NAME]_Response
  • [PROTOCOL_NAME]_[METHOD_NAME]_Error

These names use underscores unlike the others for historical reasons.

fi-0059: Invalid constant type

Not all types can used in const declarations:

library test.bad.fi0059;

const MY_CONST string:optional = "foo";

Convert to an allowed type, if possible:

library test.good.fi0059;

const MY_CONST string = "foo";

Only FIDL primitives (bool, int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64) and non-optional string types may be used in the left-hand side of a const declaration.

fi-0060: Cannot resolve constant value

Constant values must be resolvable to known values:

library test.bad.fi0060;

const MY_CONST bool = optional;

Make sure that the constant being used is a valid value:

library test.good.fi0060;

const MY_CONST bool = true;

This error often accompanies other errors, which provide more information on the nature of the non-resolvable expected constant.

fi-0061: Or operator on non-primitive values

The binary-or operator can only be used on primitives:

library test.bad.fi0061;

const HI string = "hi";
const THERE string = "there";
const OR_OP string = HI | THERE;

Try representing the data being operated on as a bits enumeration instead:

library test.good.fi0061;

type MyBits = flexible bits {
    HI = 0x1;
    THERE = 0x10;
};
const OR_OP MyBits = MyBits.HI | MyBits.THERE;

fi-0062: Newtypes are not allowed

Newtypes from RFC-0052: Type aliasing and new types are not fully implementented and cannot be used yet:

library test.bad.fi0062;

type Matrix = array<float64, 9>;

In the meantime, you can achieve something similar by defining a struct with a single element:

library test.good.fi0062a;

type Matrix = struct {
    elements array<float64, 9>;
};

Alternatively, you can define an alias, but note that unlike a newtype this provides no type safety (that is, it can be used interchangeably with its underlying type):

library test.good.fi0062b;

alias Matrix = array<float64, 9>;

fi-0063: Expected value but got type

The right-hand side of a const declaration must resolve to a constant value, not a type:

library test.bad.fi0063;

type MyType = struct {};
const MY_CONST uint32 = MyType;

Ensure that the right-hand side is a value:

library test.good.fi0063;

const MY_VALUE uint32 = 8;
const MY_CONST uint32 = MY_VALUE;

fi-0064: Incorrect bits or enum value type

When using a bits or enum variant as the value in a const declaration, the type of the bits/enum value must be the same as the left-hand side of the const declaration:

library test.bad.fi0064;

type MyEnum = enum : int32 {
    VALUE = 1;
};
type OtherEnum = enum : int32 {
    VALUE = 5;
};
const MY_CONST MyEnum = OtherEnum.VALUE;

One solution is to change the const declaration's type to match that of the value being stored:

library test.good.fi0064;

type MyEnum = enum : int32 {
    VALUE = 1;
};
type OtherEnum = enum : int32 {
    VALUE = 5;
};
const MY_CONST OtherEnum = OtherEnum.VALUE;

Alternatively, a different value can be selected to match the const declaration's type:

library test.good.fi0064;

type MyEnum = enum : int32 {
    VALUE = 1;
};
type OtherEnum = enum : int32 {
    VALUE = 5;
};
const MY_CONST MyEnum = MyEnum.VALUE;

fi-0065: Cannot convert value to expected type

A constant value must be of a type appropriate for the location it is being used.

The most common cause for this error is when a const declaration's value does not match its stated type:

library test.bad.fi0065a;

const MY_CONST bool = "foo";

This can still be problematic when a correctly defined const value is used in a location where its underlying type is invalid:

library test.bad.fi0065b;

const ONE uint8 = 0x0001;
const TWO_FIFTY_SIX uint16 = 0x0100;
const TWO_FIFTY_SEVEN uint8 = ONE | TWO_FIFTY_SIX;

Additionally, FIDL's official check their arguments against a schema. Because these arguments are themselves constant values, the same kind of type mismatch can occur:

library test.bad.fi0065c;

protocol MyProtocol {
    @selector(3840912312901827381273)
    MyMethod();
};

In all of these cases, the solution is to use only use values of the expected type in locations where const values are accepted. The above cases become, respectively:

library test.good.fi0065a;

const MY_CONST string = "foo";
library test.good.fi0065b;

const ONE uint8 = 0x0001;
const TWO_FIFTY_SIX uint16 = 0x0100;
const TWO_FIFTY_SEVEN uint16 = ONE | TWO_FIFTY_SIX;
library test.good.fi0065c;

protocol MyProtocol {
    @selector("MyOldMethod")
    MyMethod();
};

fi-0066: Constant overflows type

A constant value cannot fall outside of the range inherent to its underlying type:

library test.bad.fi0066;

const NUM uint64 = -42;

The problem may be fixed either by changing the value to fit within the type's range:

library test.good.fi0066a;

const NUM uint64 = 42;

Or otherwise by changing to the type to accommodate the currently overflowing value:

library test.good.fi0066b;

const NUM int64 = -42;

This error exclusively concerns FIDL's numeric types, all of which have the capacity to overflow. The ranges are sourced from the C++ std::numeric_limits interface and are as follows:

Type Minimum Maximum
int8 -128 127
int16 32768 32767
int32 2147483648 2147483647
int64 9223372036854775808 9223372036854775807
uint8 0 255
uint16 0 65536
uint32 0 4294967295
uint64 0 18446744073709551615
float32 -3.40282e+38 3.40282e+38
float64 -1.79769e+308 1.79769e+308

fi-0067: Bits member must be power of two

The values of all members in a bits declaration must not be any number that is not a power of two:

library test.bad.fi0067;

type NonPowerOfTwo = bits : uint64 {
    THREE = 3;
};

Instead, member values should always be powers of two:

library test.good.fi0067a;

type Fruit = bits : uint64 {
    ORANGE = 1;
    APPLE = 2;
    BANANA = 4;
};

An easy way to avoid getting tripped up by this restriction is to just use bit masks, rather than decimal numbers, for bit member values:

library test.good.fi0067b;

type Life = bits {
    A = 0b000010;
    B = 0b001000;
    C = 0b100000;
};

The bits construct represents a bit array. This is the most memory-efficient way to represent a sequence of boolean flags. Because each member of the bits declaration is mapped to a specific bit of its underlying memory, the values used for that mapping must clearly identify a specific bit in an unsigned integer to assign to.

fi-0068: Flexible enums have a reserved unknown value

This error happens when you define an enum member whose value clashes with the reserved unknown value.

Flexible enums may hold values not known by the FIDL schema. In addition, flexible enums always reserve some value which will be treated as unknown. By default, that value is the maximum numerical value representable by the underlying integer type of that enum (e.g. 255 in case of uint8).

library test.bad.fi0068;

type Foo = flexible enum : uint8 {
    ZERO = 0;
    ONE = 1;
    MAX = 255;
};

To fix the error, you may remove the member or change its value:

library test.good.fi0068a;

type Foo = flexible enum : uint8 {
    ZERO = 0;
    ONE = 1;
};
library test.good.fi0068b;

type Foo = flexible enum : uint8 {
    ZERO = 0;
    ONE = 1;
    MAX = 254;
};

Finally, if you come across this error when transitioning a strict enum to a flexible enum, you may use the @unknown attribute to designate the numerical value of a particular member as the unknown value. See @unknown.

fi-0069: Bits must use unsigned integral subtype

Using signed numerics as the underlying type for a bits declaration is prohibited:

library test.bad.fi0069;

type Fruit = bits : int64 {
    ORANGE = 1;
    APPLE = 2;
    BANANA = 4;
};

Instead, use any one of the following: uint8, uint16, uint32, or uint64:

library test.good.fi0069;

type Fruit = bits : uint64 {
    ORANGE = 1;
    APPLE = 2;
    BANANA = 4;
};

Unlike enum declarations, which allow both signed and unsigned integers (see: fi-0070), bits declarations only permit the latter. This is because each bits member necessarily represents a specific underlying bit in a bit array (this is the reason that fi-0067 exists). This is most cleanly represented as a single unsigned integer. The binary representation of unsigned integer maps directly to a single bit (2 to the power of its index), whereas the negative numbers in signed integers almost always select multiple bits due to the mechanics of the two's complement representation.

fi-0070: Enum must use integral subtype

Using the non-integral numerics float32 or float64 as the underlying type for an enum declaration is prohibited:

library test.bad.fi0070;

type MyEnum = enum : float64 {
    ONE_POINT_FIVE = 1.5;
};

Instead, use any one of the following: int8, int16,int32,int64, uint8, uint16, uint32, or uint64:

library test.good.fi0070;

type MyEnum = enum : uint64 {
    ONE = 1;
};

fi-0071: Unknown attribute disallowed on strict enum members

A strict enum must not have any members annotated with the @unknown attribute:

library test.bad.fi0071;

type MyEnum = strict enum : int8 {
    @unknown
    UNKNOWN = 0;
    FOO = 1;
    MAX = 127;
};

To continue using the @unknown attribute, change to a flexible enum:

library test.good.fi0071a;

type MyEnum = flexible enum : int8 {
    @unknown
    UNKNOWN = 0;
    FOO = 1;
    MAX = 127;
};

Otherwise, just remove the attribute altogether to remain a strict enum:

library test.good.fi0071;

type MyEnum = strict enum : int8 {
    UNKNOWN = 0;
    FOO = 1;
    MAX = 127;
};

The purpose of the @unknown attribute is to smooth over a transition from a strict enum with a user-defined unknown value, like this into a flexible enum with unknown values known to and handled by FIDL. In the above example, it would be used to transition from the second correct usage to the first one.

fi-0072: Only enum member can carry the unknown attribute

Adorning multiple enum members with the @unknown attribute is prohibited:

library test.bad.fi0072;

type MyEnum = flexible enum : uint8 {
    @unknown
    UNKNOWN = 0;
    @unknown
    OTHER = 1;
};

Choose and annotate only the member that is used as a domain specific "unknown" value:

library test.good.fi0071a;

type MyEnum = flexible enum : int8 {
    @unknown
    UNKNOWN = 0;
    OTHER = 1;
};

The purpose of the @unknown attribute is to smooth over a transition from a strict enum with a user-defined unknown value, like this into a flexible enum with unknown values known to and handled by FIDL:

library test.good.fi0072;

type MyEnum = strict enum : int8 {
    UNKNOWN = 0;
    OTHER = 1;
};
library test.good.fi0071a;

type MyEnum = flexible enum : int8 {
    @unknown
    UNKNOWN = 0;
    OTHER = 1;
};

fi-0073: Composing non-protocol

Only protocols can be used in compose statements:

library test.bad.fi0073;

type MyStruct = struct {};

protocol MyProtocol {
    compose MyStruct;
};

Make sure the name you are referring to points to a protocol:

library test.good.fi0073;

protocol MyOtherProtocol {};

protocol MyProtocol {
    compose MyOtherProtocol;
};

fi-0074: Invalid layout used for method payload

Only struct, table, or union layouts may be used to describe method payloads:

library test.bad.fi0074;

protocol MyProtocol {
    MyMethod(enum {
        FOO = 1;
    });
};

Use one of those layouts instead:

library test.good.fi0074;

protocol MyProtocol {
    MyMethod(struct {
        foo bool;
    });
};

fi-0075: Invalid primitive used for method payload

Primitives cannot be used as method method payload:

library test.bad.fi0075;

protocol MyProtocol {
    MyMethod(uint32);
};

Use a type of the struct, table, or union layout instead:

library test.good.fi0075;

protocol MyProtocol {
    MyMethod(struct {
        wrapped_in_struct uint32;
    });
};

For cases where the desirable payload really is just a primitive value, and future evolution is not a concern, wrapping the value in a struct layout will result in a payload that is the same size as desired value is by itself.

fi-0076

fi-0077: Interaction payload cannot be empty struct

The payloads in a method or event cannot be empty structs:

library test.bad.fi0077a;

protocol Test {
    MyMethod(struct {}) -> (struct {});
};
library test.bad.fi0077b;

protocol Test {
    -> MyEvent(struct {});
};

If you would like to express that a particular request/response does not hold any information, delete the empty struct, leaving () in that location:

library test.good.fi0077a;

protocol Test {
    MyMethod() -> ();
};
library test.good.fi0077b;

protocol Test {
    -> MyEvent();
};

Empty structs cannot be extended, and take 1 byte on the wire. Since FIDL supports interactions without payloads, using empty structs this way is superfluous and less efficient. Therefore they are not allowed.

fi-0078

fi-0079

fi-0080: Generated zero value ordinal

This error should never occur. If you managed to make it happen, congratulations, you've probably broken SHA-256!

Joking aside, this error occurs if the fidlc compiler generates an ordinal value of 0. It should never happen, so if it does, you've probably found a bug in the FIDL compiler. Please report the issue to our issue tracker if this happens.

fi-0081: Duplicate method ordinal

This error usually occurs when you use an @selector attribute to make two method names produce the same ordinal.

library test.bad.fi0081;

protocol Parser {
    ParseLine();

    // Multiple methods with the same ordinal...
    @selector("ParseLine")
    ParseOneLine();
};

To fix this issue, update either the method names, or selectors to not collide.

library test.good.fi0081;

protocol Parser {
    ParseLine();

    @selector("Parse1Line")
    ParseOneLine();
};

This error can also happen if there is a SHA-256 collision, but the chances of this are basically zero. If you are positive your selectors aren't at fault and you still run into this error, you've probably found a bug in the FIDL compiler. Please report the issue to our issue tracker if this happens.

fi-0082: Invalid selector value

This error occurs when you use an invalid value for an @selector. Most commonly, this is due to a typo. A selector must either be a standalone method name, or a fully qualified method name.

library test.bad.fi0082;

protocol Parser {
    @selector("test.old.fi0082.Parser.Parse")
    Parse();
};

To fix this, update the selector to either be a valid standalone, or fully qualified method name:

library test.good.fi0082;

protocol Parser {
    @selector("test.old.fi0082/Parser.Parse")
    Parse();
};

fi-0083: fuchsia.io must use explicit ordinals

The FIDL compiler used to automatically rename fuchsia.io ordinals to fuchsia.io1. This magic was intended to make it easier to migrate to fuchsia.io2 by letting the io2 versions of the methods have the "normal" ordinal. However, this system ended up being a bit too magical so it is now required to manually provide the ordinal for fuchsia.io.

library fuchsia.io;

protocol SomeProtocol {
    SomeMethod();
};

To fix this issue, manually provide a selector using fuchsia.io1 as the library name to allow the fuchsia.io names to be used for io2.

library fuchsia.io;

protocol SomeProtocol {
    @selector("fuchsia.io1/SomeProtocol.SomeMethod")
    SomeMethod();
};

fi-0084: Default members disallowed on method payload structs

Structs used as method paylods may not specify default members:

library test.bad.fi0084;

type MyStruct = struct {
    @allow_deprecated_struct_defaults
    a bool = false;
};

protocol MyProtocol {
    MyMethod(MyStruct) -> (MyStruct);
};

Remove the default members from the relevant struct declaration:

library test.good.fi0084;

type MyStruct = struct {
    a bool;
};

protocol MyProtocol {
    MyMethod(MyStruct) -> (MyStruct);
};

fi-0085

fi-0086

fi-0087

fi-0088: Service members cannot be optional

This error occurs when you mark a service member as optional. Marking a service member as optional isn't allowed because service members are always optional.

library test.bad.fi0088;

protocol Sorter {
    Sort(struct {
        input vector<int32>;
    }) -> (struct {
        output vector<int32>;
    });
};

service SortService {
    quicksort client_end:<Sorter, optional>;
    mergesort client_end:<Sorter, optional>;
};

To fix this, remove the optional clause:

library test.good.fi0088;

protocol Sorter {
    Sort(struct {
        input vector<int32>;
    }) -> (struct {
        output vector<int32>;
    });
};

service SortService {
    quicksort client_end:Sorter;
    mergesort client_end:Sorter;
};

fi-0089

fi-0090

fi-0091: Invalid struct member type

This error occurs when you try to set a default struct value for a non-supported type. Only numeric and boolean types are allowed to set a default struct value.

library test.bad.fi0091;

type Person = struct {
    @allow_deprecated_struct_defaults
    name string:optional = "";
};

To fix this, remove the default value:

library test.good.fi0091;

type Person = struct {
    @allow_deprecated_struct_defaults
    name string:optional;
};

fi-0092: Table ordinal too large

FIDL table ordinals cannot be greater than 64:

library test.bad.fi0092;

type Table64thField = table {
    1: x int64;
};

type Example = table {
    1: v1 int64;
    2: v2 int64;
    3: v3 int64;
    4: v4 int64;
    5: v5 int64;
    6: v6 int64;
    7: v7 int64;
    8: v8 int64;
    9: v9 int64;
   10: v10 int64;
   11: v11 int64;
   12: v12 int64;
   13: v13 int64;
   14: v14 int64;
   15: v15 int64;
   16: v16 int64;
   17: v17 int64;
   18: v18 int64;
   19: v19 int64;
   20: v20 int64;
   21: v21 int64;
   22: v22 int64;
   23: v23 int64;
   24: v24 int64;
   25: v25 int64;
   26: v26 int64;
   27: v27 int64;
   28: v28 int64;
   29: v29 int64;
   30: v30 int64;
   31: v31 int64;
   32: v32 int64;
   33: v33 int64;
   34: v34 int64;
   35: v35 int64;
   36: v36 int64;
   37: v37 int64;
   38: v38 int64;
   39: v39 int64;
   40: v40 int64;
   41: v41 int64;
   42: v42 int64;
   43: v43 int64;
   44: v44 int64;
   45: v45 int64;
   46: v46 int64;
   47: v47 int64;
   48: v48 int64;
   49: v49 int64;
   50: v50 int64;
   51: v51 int64;
   52: v52 int64;
   53: v53 int64;
   54: v54 int64;
   55: v55 int64;
   56: v56 int64;
   57: v57 int64;
   58: v58 int64;
   59: v59 int64;
   60: v60 int64;
   61: v61 int64;
   62: v62 int64;
   63: v63 int64;
    // The 64th field of a table must be another table, otherwise it will cause
    // fi-0093: Max Ordinal In Table Must Be Table.
   64: v64 Table64thField;
   65: v65 int64;
};

In order to allow growth beyond 64 ordinals, FIDL requires the last field of a table to be another table. Any table fields beyond 64 must be placed in a nested table.

library test.good.fi0092;

type Table64thField = table {
    1: x int64;
    // Any fields beyond 64 of table Example must be move to the nested table in
    // ordinal 64 of Example.
    2: v65 int64;
};

type Example = table {
    1: v1 int64;
    2: v2 int64;
    3: v3 int64;
    4: v4 int64;
    5: v5 int64;
    6: v6 int64;
    7: v7 int64;
    8: v8 int64;
    9: v9 int64;
   10: v10 int64;
   11: v11 int64;
   12: v12 int64;
   13: v13 int64;
   14: v14 int64;
   15: v15 int64;
   16: v16 int64;
   17: v17 int64;
   18: v18 int64;
   19: v19 int64;
   20: v20 int64;
   21: v21 int64;
   22: v22 int64;
   23: v23 int64;
   24: v24 int64;
   25: v25 int64;
   26: v26 int64;
   27: v27 int64;
   28: v28 int64;
   29: v29 int64;
   30: v30 int64;
   31: v31 int64;
   32: v32 int64;
   33: v33 int64;
   34: v34 int64;
   35: v35 int64;
   36: v36 int64;
   37: v37 int64;
   38: v38 int64;
   39: v39 int64;
   40: v40 int64;
   41: v41 int64;
   42: v42 int64;
   43: v43 int64;
   44: v44 int64;
   45: v45 int64;
   46: v46 int64;
   47: v47 int64;
   48: v48 int64;
   49: v49 int64;
   50: v50 int64;
   51: v51 int64;
   52: v52 int64;
   53: v53 int64;
   54: v54 int64;
   55: v55 int64;
   56: v56 int64;
   57: v57 int64;
   58: v58 int64;
   59: v59 int64;
   60: v60 int64;
   61: v61 int64;
   62: v62 int64;
   63: v63 int64;
   64: v64 Table64thField;
};

Every field in a table incurs the overhead of a FIDL envelope in order to allow the field to be optional. This allows every field of a table to be present or absent and enables tables to be evolved by adding or removing fields, but at the cost of much greater memory overhead than structs.

In general you can both avoid this error and reduce overhead by avoiding having small, granular fields in tables. Instead you can group together elements that you expect will need to be added or removed simultaneously into structs and use those as the fields of the table. This reduces overhead and avoid running out of ordinals at the cost of some evolvability.

This became an error in RFC-0132: FIDL table size limit, which was intended to prevent users from accidentally incurring the overhead of very large tables. This extra cost isn't obvious in the schema, especially when there are only a few fields (with large ordinals), or where there are many fields but only a few are used at a time.

fi-0093: Max ordinal in table must be table

The type of the 64th member in a FIDL table must itself be a table:

library test.bad.fi0093;

type Example = table {
    1: v1 int64;
    2: v2 int64;
    3: v3 int64;
    4: v4 int64;
    5: v5 int64;
    6: v6 int64;
    7: v7 int64;
    8: v8 int64;
    9: v9 int64;
   10: v10 int64;
   11: v11 int64;
   12: v12 int64;
   13: v13 int64;
   14: v14 int64;
   15: v15 int64;
   16: v16 int64;
   17: v17 int64;
   18: v18 int64;
   19: v19 int64;
   20: v20 int64;
   21: v21 int64;
   22: v22 int64;
   23: v23 int64;
   24: v24 int64;
   25: v25 int64;
   26: v26 int64;
   27: v27 int64;
   28: v28 int64;
   29: v29 int64;
   30: v30 int64;
   31: v31 int64;
   32: v32 int64;
   33: v33 int64;
   34: v34 int64;
   35: v35 int64;
   36: v36 int64;
   37: v37 int64;
   38: v38 int64;
   39: v39 int64;
   40: v40 int64;
   41: v41 int64;
   42: v42 int64;
   43: v43 int64;
   44: v44 int64;
   45: v45 int64;
   46: v46 int64;
   47: v47 int64;
   48: v48 int64;
   49: v49 int64;
   50: v50 int64;
   51: v51 int64;
   52: v52 int64;
   53: v53 int64;
   54: v54 int64;
   55: v55 int64;
   56: v56 int64;
   57: v57 int64;
   58: v58 int64;
   59: v59 int64;
   60: v60 int64;
   61: v61 int64;
   62: v62 int64;
   63: v63 int64;
   64: v64 int64;
};

Users that find themselves needing to add a 64th member should create a separate table to hold members 64 and beyond, and place that member in the table instead:

library test.good.fi0093;

type Table64thField = table {
    1: x int64;
};

type Example = table {
    1: v1 int64;
    2: v2 int64;
    3: v3 int64;
    4: v4 int64;
    5: v5 int64;
    6: v6 int64;
    7: v7 int64;
    8: v8 int64;
    9: v9 int64;
   10: v10 int64;
   11: v11 int64;
   12: v12 int64;
   13: v13 int64;
   14: v14 int64;
   15: v15 int64;
   16: v16 int64;
   17: v17 int64;
   18: v18 int64;
   19: v19 int64;
   20: v20 int64;
   21: v21 int64;
   22: v22 int64;
   23: v23 int64;
   24: v24 int64;
   25: v25 int64;
   26: v26 int64;
   27: v27 int64;
   28: v28 int64;
   29: v29 int64;
   30: v30 int64;
   31: v31 int64;
   32: v32 int64;
   33: v33 int64;
   34: v34 int64;
   35: v35 int64;
   36: v36 int64;
   37: v37 int64;
   38: v38 int64;
   39: v39 int64;
   40: v40 int64;
   41: v41 int64;
   42: v42 int64;
   43: v43 int64;
   44: v44 int64;
   45: v45 int64;
   46: v46 int64;
   47: v47 int64;
   48: v48 int64;
   49: v49 int64;
   50: v50 int64;
   51: v51 int64;
   52: v52 int64;
   53: v53 int64;
   54: v54 int64;
   55: v55 int64;
   56: v56 int64;
   57: v57 int64;
   58: v58 int64;
   59: v59 int64;
   60: v60 int64;
   61: v61 int64;
   62: v62 int64;
   63: v63 int64;
   64: v64 Table64thField;
};

The reasoning and motivations behind this requirement are fully elaborated in RFC-0132: FIDL table size limit. In short, FIDL tables need to have relatively constrained limits on the number of fields that they allow, otherwise the encoded form of a table that only uses a few fields at a time would have an unacceptably large amount of dead space (16 bytes for every omitted member).

To give users who want more than 64 fields in their tables a workaround, FIDL forces the last ordinal to be reserved for a "continuation table" that contains the extra fields. Using any other type in this position would render the table unextendable going forward.

fi-0094: Duplicate table member ordinals

The ordinals used for members in a table declaration cannot be repeated:

library test.bad.fi0094;

type MyTable = table {
    1: my_field string;
    1: my_other_field uint32;
};

Increment the ordinals as needed to ensure that the declaration has unique ordinals for all of its members:

library test.good.fi0094a;

type MyTable = table {
    1: my_field string;
    2: my_other_field uint32;
};

Alternatively, one of the members with the duplicated name can be removed:

library test.good.fi0094b;

type MyTable = table {
    1: my_field string;
};

The ordinal is used to identify the field on the wire. If two members share an ordinal, there is no reliable way to tell which field is being referred to when decoding a FIDL message.

fi-0095

fi-0096

fi-0097: Duplicate union member ordinals

The ordinals used for members in a union declaration cannot be repeated:

library test.bad.fi0097;

type MyUnion = strict union {
    1: my_variant string;
    1: my_other_variant int32;
};

Increment the ordinals as needed to ensure that the declaration has unique ordinals for all of its members:

library test.good.fi0097a;

type MyUnion = strict union {
    1: my_variant string;
    2: my_other_variant int32;
};

Alternatively, one of the members with the duplicated name can be removed:

library test.good.fi0097b;

type MyUnion = strict union {
    1: my_variant string;
};

The ordinal is used to identify the variant on the wire. If two members share an ordinal, there is no reliable way to tell which variant is being referred to when decoding a FIDL message.

fi-0098

fi-0099

fi-0100

fi-0101: Unresolvable size constraint

The size constraint applied to a vector or string type definition must be a valid value of the uint32 type:

library test.bad.fi0101a;

alias MyBoundedOptionalVector = vector<uint32>:<"255", optional>;
library test.bad.fi0101b;

alias MyBoundedOptionalVector = vector<uint32>:<uint8, optional>;

Ensure that this is the case:

library test.good.fi0101;

alias MyBoundedOptionalVector = vector<uint32>:<255, optional>;

fi-0102: Unresolvable member value

The members of bits and enum declarations must be resolvable values of the specified subtype:

library test.bad.fi0102;

type Fruit = bits : uint64 {
    ORANGE = 1;
    APPLE = 2;
    BANANA = -4;
};

Ensure that all values match the underlying type of the declaration:

library test.good.fi0102;

type Fruit = bits : uint64 {
    ORANGE = 1;
    APPLE = 2;
    BANANA = 4;
};

fi-0103: Unresolvable struct default value

The default values for the members of struct declarations must match their respective member's stated type:

library test.bad.fi0103;

type MyEnum = enum : int32 {
    A = 1;
};

type MyStruct = struct {
    @allow_deprecated_struct_defaults
    field MyEnum = 1;
};

Ensure that the value matches the declared type:

library test.good.fi0103;

type MyEnum = enum : int32 {
    A = 1;
};

type MyStruct = struct {
    @allow_deprecated_struct_defaults
    field MyEnum = MyEnum.A;
};

fi-0104: Unresolvable attribute argument

Values of arguments for official FIDL attributes cannot be invalid per the attribute schema's expectation for that argument:

library test.bad.fi0104;

type MyStruct = struct {
    my_field @generated_name(true) struct {};
};

Ensure that the type of value being used as an attribute argument is correct:

library test.good.fi0104;

type MyStruct = struct {
    my_field @generated_name("my_inner_type") struct {};
};

fi-0105

fi-0106

fi-0107: Duplicate member values

Neither bits nor enum declarations can have members with the same value:

library test.bad.fi0107;

type Fruit = flexible enum {
    ORANGE = 1;
    APPLE = 1;
};

Change the member values to all be unique:

library test.good.fi0107a;

type Fruit = flexible enum {
    ORANGE = 1;
    APPLE = 2;
};

Alternatively, remove one of the duplicated members:

library test.good.fi0107b;

type Fruit = flexible enum {
    ORANGE = 1;
};

fi-0108

fi-0109

fi-0110: Resource containing types must be marked resource

A type that includes a handle, either directly or through the transitive inclusion of another handle-containing type, cannot be declared without that type being specified as a resource:

library test.bad.fi0110;

using zx;

type Foo = struct {
    handle zx.Handle;
};

There are two possible solutions. The first is to annotate the offending declaration with the resource modifier:

library test.good.fi0110a;

using zx;

type Foo = resource struct {
    handle zx.Handle;
};

Alternatively, one could opt to remove the resource-including type completely, thereby obviating the need for the modifier on the owning declaration:

library test.good.fi0110b;

type Foo = struct {
    value uint32;
};

The reasoning and motivations behind the addition of the resource modifier and the "infectious" nature of the usage pattern enforced by this error can be found in RFC-0057: Default no handles.

fi-0111: Inline size exceeds limit

FIDL types whose inline size is 64 KiB or more are not allowed:

library test.bad.fi0111;

type MyStruct = struct {
    numbers array<uint8, 65536>;
};

Instead, make sure the type has an inline size less than 64 KiB. In this case, we can adjust the array bound:

library test.good.fi0111;

type MyStruct = struct {
    numbers array<uint8, 65535>;
};

This limit exists for performance reasons. It means that encoders and decoders can assume sizes and offsets fit in unsigned 16 bit integers.

You are unlikely to run into this in practice unless you use large arrays or deeply nested structs. Most FIDL constructs (such as strings, vectors, tables, and unions) use out-of-line storage, which does not count towards their individual inline size.

fi-0112: Service member is not a client_end

Service members are only allowed to be client ends, not any other type:

library test.bad.fi0112;

protocol Calculator {};

service Service {
    calculator server_end:Calculator;
};

To fix the error, make sure the member has the form client_end:P for some protocol P:

library test.good.fi0112;

protocol Calculator {};

service Service {
    calculator client_end:Calculator;
};

A service is a collection of protocol instances, not a general purpose data structure, so it would not make sense for one to allow arbitrary types.

fi-0113: Mismatched transport in service

A FIDL service is not allowed to contain protocols using different transports:

library test.bad.fi0113;

protocol ChannelProtocol {};

@transport("Driver")
protocol DriverProtocol {};

service SomeService {
    a client_end:ChannelProtocol;
    b client_end:DriverProtocol;
};

Instead, use separate services for each transport:

library test.good.fi0113;

protocol ChannelProtocol {};

@transport("Driver")
protocol DriverProtocol {};

service ChannelService {
    protocol client_end:ChannelProtocol;
};

service DriverService {
    protocol client_end:DriverProtocol;
};

Note that services are an unfinished feature in FIDL. They were originally designed in RFC-0041: Support for unifying serviceas and devices. See https://fxbug.dev/42160684 for the status as of October 2022.

fi-0114: Composed protocol is too open

A protocol cannot compose another protocol that is more open than itself:

library test.bad.fi0114;

open protocol Composed {};

ajar protocol Composing {
    compose Composed;
};

You can fix this by increasing the openness of the composing protocol, i.e. changing it from closed to ajar or from ajar to open:

library test.good.fi0114a;

open protocol Composed {};

open protocol Composing {
    compose Composed;
};

Alternatively, you can reduce the openness of the composed protocol, i.e. change it from open to ajar or from ajar to closed:

library test.good.fi0114b;

ajar protocol Composed {};

ajar protocol Composing {
    compose Composed;
};

This rule exists because the openness of a protocol restricts what kind of methods it is allowed to contain. For example, an ajar protocol cannot contain flexible two-way methods, but an open protocol can, so it's not safe for an ajar protocol to compose an open protocol.

See RFC-0138: Handling unknown interactions for more information about protocol modifiers.

fi-0115: Flexible two-way method in requires open protocol

Closed and ajar protocols are not allowed to contain flexible two-way methods:

library test.bad.fi0115;

ajar protocol Protocol {
    flexible Method() -> ();
};

Instead, mark the two-way method strict instead of flexible:

library test.good.fi0115a;

ajar protocol Protocol {
    strict Method() -> ();
};

Alternatively, mark the protocol open instead of closed or ajar:

library test.good.fi0115b;

open protocol Protocol {
    flexible Method() -> ();
};

This error exists because the purpose of the closed (or ajar) modifier is to make sure a method does not contain any flexible (two-way) methods. When first creating a protocol, you should carefully think about whether it should be closed, ajar, or open based on the evolvability properties you need from it.

See RFC-0138: Handling unknown interactions for more information about protocol modifiers.

fi-0116: Flexible one-way method requires ajar or open protocol

Closed protocols are not allowed to contain flexible one-way methods:

library test.bad.fi0116;

closed protocol Protocol {
    flexible Method();
};

Instead, mark the one-way method strict instead of flexible:

library test.good.fi0116;

closed protocol Protocol {
    strict Method();
};

Alternatively, mark the protocol ajar or open instead of closed:

library test.good.fi0116;

ajar protocol Protocol {
    flexible Method();
};

This error exists because the purpose of the closed modifier is to make sure a method does not contain any flexible methods. When first creating a protocol, you should carefully think about whether it should be closed, ajar, or open based on the evolvability properties you need from it.

See RFC-0138: Handling unknown interactions for more information about protocol modifiers.

fi-0117: Handle used in incompatible transport

Protocols can only refer to handles that are compatible with their transport. For example, a protocol over the Zircon channel transport cannot refer to Fuchsia Driver Framework handles:

library test.bad.fi0117;

using fdf;

protocol Protocol {
    Method(resource struct {
        h fdf.handle;
    });
};

Instead, use handles that are compatible with the protocol's transport:

library test.good.fi0117a;

using zx;

protocol Protocol {
    Method(resource struct {
        h zx.Handle;
    });
};

Alternatively, change the transport of the protocol to match the handles:

library test.good.fi0117b;

using fdf;

@transport("Driver")
protocol Protocol {
    Method(resource struct {
        h fdf.handle;
    });
};

fi-0118: Transport end used in incompatible transport

Protocols can only refer to transport ends (client_end and server_end) of protocols over the same transport. For example, a protocol using the Syscall transport cannot refer to a client end of a protocol using the Driver transport:

library test.bad.fi0118;

@transport("Driver")
protocol DriverProtocol {};

@transport("Syscall")
protocol P {
    M(resource struct {
        s client_end:DriverProtocol;
    });
};

To fix the error, remove the transport end member:

library test.good.fi0118;

@transport("Driver")
protocol DriverProtocol {};

@transport("Syscall")
protocol Protocol {
    M();
};

fi-0119: Error syntax in events is deprecated

Events used to allow the error syntax, but this is now deprecated:

library example;

protocol MyProtocol {
    -> OnMyEvent() error int32;
};

Instead, specify the event without an error:

library test.good.fi0119a;

protocol MyProtocol {
    -> OnMyEvent();
};

Alternatively, model the error as a union instead:

library test.good.fi0119b;

protocol MyProtocol {
    -> OnMyEvent(flexible union {
        1: success struct {};
        2: error uint32;
    });
};

Using the error syntax with events was already an unusual thing to do before it was deprecated. Events are server initiated, and it seems strange for the server to send an unsolicited failure to the client. It is better to use a union for these rare use cases rather than having to support a language feature in all the bindings.

fi-0120: Invalid attribute placement

Some official attributes are only allowed in certain places. For example, the @selector attribute can only be used on methods:

library test.bad.fi0120a;

@selector("Nonsense")
type MyUnion = union {
    1: hello uint8;
};

To fix the error, remove the attribute:

library test.good.fi0120a;

type MyUnion = union {
    1: hello uint8;
};

You might also encounter this error when intending to use an attribute in a way that is supported, but placing it in the wrong spot. For example, the @generated_name attribute cannot go directly on a member:

library test.bad.fi0120a;

@selector("Nonsense")
type MyUnion = union {
    1: hello uint8;
};

Instead, it should go just before the member's anonymous layout:

library test.good.fi0120a;

type MyUnion = union {
    1: hello uint8;
};

fi-0121: Deprecated attribute

Some official attributes are deprecated and should no longer be used:

library test.bad.fi0121;

@example_deprecated_attribute
type MyStruct = struct {};

The fix depends on why the attribute was deprecated. For example, the error message might say to use a different attribute instead. In this case, we can just remove the attribute:

library test.good.fi0121;

type MyStruct = struct {};

fi-0122: Duplicate attribute name

An element cannot have multiple attributes with the same name:

library test.bad.fi0122;

@custom_attribute("first")
@custom_attribute("second")
type Foo = struct {};

Instead, specify each attribute only once:

library test.good.fi0122;

@custom_attribute("first")
type Foo = struct {};

fi-0123: Duplicate canonical attribute name

An element cannot have multiple attributes with the same canonical name:

library test.bad.fi0123;

@custom_attribute("first")
@CustomAttribute("second")
type Foo = struct {};

Even though custom_attribute and CustomAttribute look different, they are both represented by the canonical name custom_attribute. You get the canonical name by converting the original name to snake_case.

To fix the error, give each attribute a name that is unique after canonicalization.

library test.good.fi0123;

@custom_attribute("first")
@AnotherCustomAttribute("first")
type Foo = struct {};

See fi-0035 for more details on why FIDL requires declarations to have unique canonical names.

fi-0124: Custom attribute argument must be string or bool

The arguments on user-defined FIDL attributes are limited to being string or boolean types:

library test.bad.fi0124;

@my_custom_attr(foo=1, bar=2.3)
type MyStruct = struct {};
library test.good.fi0124;

@my_custom_attr(foo=true, bar="baz")
type MyStruct = struct {};

Unlike official attributes, the schema of user defined attributes is not known to the compiler. Because of this, it is impossible for the compiler to deduce the type of any given numeric argument - is 2 an int8, a uint64, or a float32? There's no way for the compiler to know.

A possible solution to this problem would be to implement a first-class numeric type in the JSON IR for ambiguous cases like these. However, because custom attribute arguments are the only known use case for this today, this feature has not been prioritized.

fi-0125: Attribute argument must not be named

When using an official attribute that takes a single argument, you cannot name that argument:

library test.bad.fi0125;

@discoverable(value="example.Bar")
protocol Foo {};

Instead, pass the argument without giving it a name:

library test.good.fi0125;

@discoverable("example.Bar")
protocol Foo {};

FIDL enforces this to make attributes more concise and consistent. Under the hood, the argument name is inferred to be value (and this will show up in the JSON IR) because that is the only argument the attribute takes.

fi-0126: Attribute argument must be named

When using an official attribute that takes multiple arguments, you cannot pass an unnamed argument:

@available(1)
library test.bad.fi0126;

Instead, specify the name of the argument:

@available(added=1)
library test.good.fi0126;

This error occurs because there is no way of knowing what argument you intended to set if the attribute accepts more than one argument.

fi-0127: Missing required attribute argument

When using an official attribute that has a required argument, you cannot omit it:

library test.bad.fi0127;

@has_required_arg
type Foo = struct {};

Instead, provide the required argument:

library test.good.fi0127;

@has_required_arg(required="something")
type Foo = struct {};

fi-0128: Missing single attribute argument

When using an official attribute that requires a single argument, you cannot omit it:

library test.bad.fi0128;

@transport
protocol Protocol {};

Instead, provide an argument:

library test.good.fi0128;

@transport("Driver")
protocol Protocol {};

fi-0129: Unknown attribute argument

When using an official attribute, you cannot provide an argument that is not in its schema:

@available(added=1, discontinued=2)
library test.bad.fi0129;

If you meant to pass a different argument and got the name wrong, change it to use the correct name:

@available(added=1, deprecated=2)
library test.good.fi0129a;

Alternatively, remove the argument:

@available(added=1)
library test.good.fi0129b;

This error occurs because official attributes are validated against a schema. If FIDL allowed arbitrary arguments, they would have no effect, and it could cause bugs by masking typos.

fi-0130: Duplicate attribute argument

An attribute cannot have two arguments with the same name:

library test.bad.fi0130;

@custom_attribute(custom_arg=true, custom_arg=true)
type Foo = struct {};

Instead, only provide one argument with that name:

library test.good.fi0130;

@custom_attribute(custom_arg=true)
type Foo = struct {};

fi-0131: Duplicate canonical attribute argument

An attribute cannot have two arguments with the same canonical name:

library test.bad.fi0131;

@custom_attribute(custom_arg=true, CustomArg=true)
type Foo = struct {};

Even though custom_arg and CustomArg look different, they are both represented by the canonical name custom_arg. You get the canonical name by converting the original name to snake_case.

To fix the error, give each argument a name that is unique after canonicalization:

library test.good.fi0131a;

@custom_attribute(custom_arg=true, AnotherCustomArg=true)
type Foo = struct {};

Alternatively, remove one of the arguments:

library test.good.fi0131b;

@custom_attribute(custom_arg=true)
type Foo = struct {};

See fi-0035 for more details on why FIDL requires declarations to have unique canonical names.

fi-0132: Unexpected attribute argument

When using an official attribute that takes no arguments, you cannot provide an argument:

library test.bad.fi0132;

type Foo = flexible enum : uint8 {
    @unknown("hello")
    BAR = 1;
};

Instead, remove the argument:

library test.good.fi0132;

type Foo = flexible enum : uint8 {
    @unknown
    BAR = 1;
};

fi-0133: Attribute argument must be literal

Certain official attributes do not allow arguments that are references to constants:

library test.bad.fi0133;

const NAME string = "MyTable";

type Foo = struct {
    bar @generated_name(NAME) table {};
};

Instead, pass a literal value as the argument:

library test.good.fi0133;

type Foo = struct {
    bar @generated_name("MyTable") table {};
};

These attributes require a literal argument because their values influence compilation. Supporting non-literal arguments would be difficult to implement, or in some cases impossible because it leads to contradictions.

fi-0134

fi-0135: Invalid discoverable name

This error occurs when you use a bad name for an @discoverable attribute. @discoverable attributes should be the library name followed by a . and the protocol name.

library test.bad.fi0135;

@discoverable("test.bad.fi0135/Parser")
protocol Parser {
    Tokenize() -> (struct {
        tokens vector<string>;
    });
};

To fix this error, use a valid discoverable name:

library test.good.fi0135;

@discoverable("test.good.fi0135.Parser")
protocol Parser {
    Tokenize() -> (struct {
        tokens vector<string>;
    });
};

fi-0136

fi-0137

fi-0138

fi-0139

fi-0140

fi-0141: Invalid error type

The error type on a method response payload must be an int32, uint32, or enum thereof:

library test.bad.fi0141;

protocol MyProtocol {
    MyMethod() -> () error float32;
};

Change the error type to one of the valid options to fix this error:

library test.good.fi0141;

protocol MyProtocol {
    MyMethod() -> () error int32;
};

See RFC-0060: Error handling for more details.

fi-0142: Invalid protocol transport type

The @transport(...) attribute on a protocol declaration must not specify an invalid transport:

library test.bad.fi0142;

@transport("Invalid")
protocol MyProtocol {
    MyMethod();
};

Use one of the supported transports instead:

library test.good.fi0142;

@transport("Syscall")
protocol MyProtocol {
    MyMethod();
};

What constitutes a supported transport is still being finalized. See the FIDL attributes for the latest information.

fi-0143

fi-0144

fi-0145: Attribute typo

An attribute name whose spelling is too similar to one of FIDL's official attributes will result in a compiler warning:

library test.bad.fi0145;

@duc("should be doc")
protocol Example {
    Method();
};

In the above example, the attribute @duc is too similar in spelling to the official FIDL attribute @doc. In cases like this, the attribute naming is intentional, and not an accidental misspelling of an official FIDL attribute, it should be modified to be sufficiently unique:

library test.good.fi0145;

@duck("quack")
protocol Example {
    Method();
};

Besides spell checking, the purpose of this warning is to discourage using names that are too similar to official FIDL attributes.

The typo detection algorithm works by calculating the edit distance of the attribute name from every official FIDL attribute. Names that are too similar, defined as having too small of an edit distance, trigger the typo detector.

fi-0146: Invalid generated name

This error occurs when you use the @generated_name attribute with an invalid name. Generated names must follow the same rules as all FIDL identifiers.

library test.bad.fi0146;

type Device = table {
    1: kind flexible enum {
        DESKTOP = 1;
        PHONE = 2;
    };
};

type Input = table {
    1: kind @generated_name("_kind") flexible enum {
        KEYBOARD = 1;
        MOUSE = 2;
    };
};

To fix this issue, change the @generated_name value to a valid identifier.

library test.good.fi0146;

type Device = table {
    1: kind flexible enum {
        DESKTOP = 1;
        PHONE = 2;
    };
};

type Input = table {
    1: kind @generated_name("input_kind") flexible enum {
        KEYBOARD = 1;
        MOUSE = 2;
    };
};

fi-0147: @available missing arguments

This error occurs when you use the @available attribute and don't provide the necessary arguments. @available requires at least one of added, deprecated, or removed.

@available(added=1)
library test.bad.fi0147;

@available
type Foo = struct {};

To fix this issue, add one of the required arguments:

@available(added=1)
library test.good.fi0147;

@available(added=2)
type Foo = struct {};

See FIDL versioning for more information.

fi-0148: Note without deprecation

This errors occurs when you use the note argument for the @available attribute without the deprecated argument. notes are only supported for deprecation.

@available(added=1, note="My note")
library test.bad.fi0148;

To fix this error, either remove the note:

@available(added=1)
library test.good.fi0148a;

or add the necessary deprecated notice:

@available(added=1, deprecated=2, note="Removed in 2; use X instead.")
library test.good.fi0148b;

See FIDL versioning for more information.

fi-0149: Platform not on library

This error occurs when you try to use the @available attribute's platform argument anywhere other than above a library declaration. The platform argument is only valid at the library level.

@available(added=1)
library test.bad.fi0149;

@available(platform="foo")
type Person = struct {
    name string;
};

To fix this, either move the platform argument to the library @available attribute:

@available(added=1, platform="foo")
library test.good.fi0149a;

type Person = struct {
    name string;
};

or remove the platform argument completely:

@available(added=1)
library test.good.fi0149b;

type Person = struct {
    name string;
};

fi-0150: Library availability missing added

This error occurs when you add an @available attribute to your library without providing an added argument. The added argument is required for library @available attributes.

@available(removed=2)
library test.bad.fi0150a;
@available(platform="foo")
library test.bad.fi0150b;

To fix this issue, add the added argument to the library's @available attribute:

@available(added=1, removed=2)
library test.good.fi0150a;
@available(added=1, platform="foo")
library test.good.fi0150b;

fi-0151: Missing library availability

This error occurs when you add an @available attribute on a non-library declaration without an @available attribute on the library declaration.

library test.bad.fi0151;

@available(added=1)
type Person = struct {
    name string;
};

To fix this error, add the @available attribute to the library declaration:

@available(added=1)
library test.good.fi0151a;

@available(added=1)
type Person = struct {
    name string;
};

or remove the @available attribute from the nonlibrary declaration:

library test.good.fi0151b;

type Person = struct {
    name string;
};

fi-0152: Invalid platform

This error occurs when you use invalid characters for the platform argument of an @available attribute. The platform argument must be a valid FIDL library identifier.

@available(added=1, platform="Spaces are not allowed")
library test.bad.fi0152;

To fix this error, remove the disallowed characters:

@available(added=1, platform="foo")
library test.good.fi0152;

fi-0153: Invalid version

This error occurs when you use an invalid version for an added or removed argument on an @available attribute. added and removed arguments must be positive integers between 1 and 2^63-1, or the special constant HEAD.

@available(added=0)
library test.bad.fi0153;

To fix this issue, change the version to a valid value:

@available(added=1)
library test.good.fi0153;

fi-0154: Invalid availability order

This error occurs when you use a bad combination of added, deprecated, and remove arguments for an @available attribute. The following constraints must be respected:

  • added must be less than or equal to deprecated
  • deprecated must be less than removed
  • added must be less than removed
@available(added=2, removed=2)
library test.bad.fi0154a;
@available(added=2, deprecated=3, removed=3)
library test.bad.fi0154b;

To fix this issue update the added, deprecated, and removed arguments to the required ordering:

@available(added=1, removed=2)
library test.good.fi0154a;
@available(added=2, deprecated=2, removed=3)
library test.good.fi0154b;

fi-0155: Availability conflicts with parent

This error occurs when you add an @availability attribute to a non-library declaration that conflicts with the library's declaration.

@available(added=2, deprecated=3, removed=4)
library test.bad.fi0155a;

@available(added=1)
type Person = struct {
    name string;
};
@available(added=2, deprecated=3, removed=4)
library test.bad.fi0155b;

@available(added=4)
type Person = struct {
    name string;
};

To fix this error, update the @availability attributes to the required constraints:

@available(added=2, deprecated=3, removed=4)
library test.good.fi0155;

@available(added=2)
type Person = struct {
    name string;
};

fi-0156: Cannot be optional

This error occurs when you try to mark a type as optional that cannot be optional.

library test.bad.fi0156;

type Person = struct {
    name string;
    age int16:optional;
};

To fix this error, remove the optional constraint:

library test.good.fi0156;

type Person = struct {
    name string;
    age int16;
};

Only FIDL types that can be made optional with no change to the wire shape are allowed to use the optional constraint. See the optionality guide, or the expandable below, for more information.

FIDL recipe: Optionality

Certain FIDL types can be made optional with no change to the wire shape of their containing message with the addition of the :optional constraint. Further, the table layout is always optional, while the struct layout never is. To make a struct optional, it must be wrapped in a box<T>, thereby changing the wire shape of its containing message.

Base type Optional version Does optionality change the wire layout?
struct {...} box<struct {...}> Yes
table {...} table {...} No
union {...} union {...}:optional No
vector<T> vector<T>:optional No
string string:optional No
zx.Handle zx.Handle:optional No
client_end:P client_end:<P, optional> No
server_end:P server_end:<P, optional> No

All other types (bits, enum, array<T, N>, and the primitive types) cannot be made optional.

In this variant, we allow our key-value store to take other key-value stores as members. In short, we turn it into a tree. We do this by replacing the original definition of value with one that utilizes a two-member union: one variant stores leaf nodes using the same vector<byte> type as before, while the other stores branch nodes in the form of other nested stores.

Reasoning

Here, we see several uses of optionality, whereby we can declare a type that may or may not exist. There are three flavors of optionality in FIDL:

  • Types that have are always stored out-of-line on the wire, and thus have a builtin way to describe "absentness" via the null envelope. Enabling optionality for these types doesn't affect the wire shape of messages they are included in - it simply changes which values are valid for that particular type. The union, vector<T>, client_end, server_end, and zx.Handle types can all be made optional via the addition of the :optional constraint. By making our value union optional, we are able to introduce a canonical "null" entry, in the form of an absent value. This means that empty bytes and absent/empty store properties are invalid values.
  • Unlike the aforementioned types, the struct layout has no extra space where a null header can be stored. Because of this, it needs to be wrapped in an envelope, changing the on-the-wire shape of the message it is being included in. To ensure that this wire-modifying effect easily legible, the Item struct type must be wrapped in a box<T> type template.
  • Finally, table layouts are always optional. An absent table is simply one with none of its members set.

Trees are a naturally self-referential data structure: any node in the tree may contain a leaf with pure data (in this case, a string), or a sub-tree with more nodes. This requires recursion: the definition of Item is now transitively dependent on itself! Representing recursive types in FIDL can be a bit tricky, especially because support is currently somewhat limited. We can support such types as long as there is at least one optional type in the cycle created by the self-reference. For instance, here we define the items struct member to be a box<Item>, thereby breaking the includes cycle.

These changes also make heavy use of anonymous types, or types whose declarations are inlined at their sole point of use, rather than being named, top-level type declarations of their own. By default, the names of anonymous types in the generated language bindings are taken from their local context. For instance, the newly introduced flexible union takes on its owning member's name Value, the newly introduced struct would become Store, and so on. Because this heuristic can sometimes cause collisions, FIDL provides an escape hatch by allowing the author to manually override an anonymous type's generated name. This is done via the @generated_name attribute, which allows one to change the name generated by backends. We can use one here, where the would-be Store type is renamed to NestedStore to prevent a name collision with the protocol declaration that uses that same name.

Implementation

The FIDL, CML, and realm interface definitions are modified as follows:

FIDL

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
library examples.keyvaluestore.supporttrees;

/// An item in the store. The key must match the regex `^[A-z][A-z0-9_\.\/]{2,62}[A-z0-9]$`. That
/// is, it must start with a letter, end with a letter or number, contain only letters, numbers,
/// periods, and slashes, and be between 4 and 64 characters long.
type Item = struct {
    key string:128;
    value strict union {
        // Keep the original `bytes` as one of the options in the new union. All leaf nodes in the
        // tree must be `bytes`, or absent unions (representing empty). Empty byte arrays are
        // disallowed.
        1: bytes vector<byte>:64000;

        // Allows a store within a store, thereby turning our flat key-value store into a tree
        // thereof. Note the use of `@generated_name` to prevent a type-name collision with the
        // `Store` protocol below, and the use of `box<T>` to ensure that there is a break in the
        // chain of recursion, thereby allowing `Item` to include itself in its own definition.
        //
        // This is a table so that added fields, like for example a `hash`, can be easily added in
        // the future.
        2: store @generated_name("nested_store") table {
            1: items vector<box<Item>>;
        };
    }:optional;
};

/// An enumeration of things that may go wrong when trying to write a value to our store.
type WriteError = flexible enum {
    UNKNOWN = 0;
    INVALID_KEY = 1;
    INVALID_VALUE = 2;
    ALREADY_EXISTS = 3;
};

/// A very basic key-value store.
@discoverable
open protocol Store {
    /// Writes an item to the store.
    flexible WriteItem(struct {
        attempt Item;
    }) -> () error WriteError;
};

CML

Client

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/client_bin",
    },
    use: [
        { protocol: "examples.keyvaluestore.supporttrees.Store" },
    ],
    config: {
        write_items: {
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
        },

        // A newline separated list nested entries. The first line should be the key
        // for the nested store, and each subsequent entry should be a pointer to a text file
        // containing the string value. The name of that text file (without the `.txt` suffix) will
        // serve as the entries key.
        write_nested: {
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
        },

        // A list of keys, all of which will be populated as null entries.
        write_null: {
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
        },

    },
}

Server

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/server_bin",
    },
    capabilities: [
        { protocol: "examples.keyvaluestore.supporttrees.Store" },
    ],
    expose: [
        {
            protocol: "examples.keyvaluestore.supporttrees.Store",
            from: "self",
        },
    ],
}

Realm

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    children: [
        {
            name: "client",
            url: "#meta/client.cm",
        },
        {
            name: "server",
            url: "#meta/server.cm",
        },
    ],
    offer: [
        // Route the protocol under test from the server to the client.
        {
            protocol: "examples.keyvaluestore.supporttrees.Store",
            from: "#server",
            to: "#client",
        },

        // Route diagnostics support to all children.
        {
            protocol: [
                "fuchsia.inspect.InspectSink",
                "fuchsia.logger.LogSink",
            ],
            from: "parent",
            to: [
                "#client",
                "#server",
            ],
        },
    ],
}

Client and server implementations can then be written in any supported language:

Rust

Client

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use {
    anyhow::{Context as _, Error},
    config::Config,
    fidl_examples_keyvaluestore_supporttrees::{Item, NestedStore, StoreMarker, Value},
    fuchsia_component::client::connect_to_protocol,
    std::{thread, time},
};

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Load the structured config values passed to this component at startup.
    let config = Config::take_from_startup_handle();

    // Use the Component Framework runtime to connect to the newly spun up server component. We wrap
    // our retained client end in a proxy object that lets us asynchronously send `Store` requests
    // across the channel.
    let store = connect_to_protocol::<StoreMarker>()?;
    println!("Outgoing connection enabled");

    // This client's structured config has one parameter, a vector of strings. Each string is the
    // path to a resource file whose filename is a key and whose contents are a value. We iterate
    // over them and try to write each key-value pair to the remote store.
    for key in config.write_items.into_iter() {
        let path = format!("/pkg/data/{}.txt", key);
        let value = std::fs::read_to_string(path.clone())
            .with_context(|| format!("Failed to load {path}"))?;
        let res = store
            .write_item(&Item {
                key: key.clone(),
                value: Some(Box::new(Value::Bytes(value.into_bytes()))),
            })
            .await;
        match res? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // Add nested entries to the key-value store as well. The entries are strings, where the first
    // line is the key of the entry, and each subsequent entry should be a pointer to a text file
    // containing the string value. The name of that text file (without the `.txt` suffix) will
    // serve as the entries key.
    for spec in config.write_nested.into_iter() {
        let mut items = vec![];
        let mut nested_store = NestedStore::default();
        let mut lines = spec.split("\n");
        let key = lines.next().unwrap();

        // For each entry, make a new entry in the `NestedStore` being built.
        for entry in lines {
            let path = format!("/pkg/data/{}.txt", entry);
            let contents = std::fs::read_to_string(path.clone())
                .with_context(|| format!("Failed to load {path}"))?;
            items.push(Some(Box::new(Item {
                key: entry.to_string(),
                value: Some(Box::new(Value::Bytes(contents.into()))),
            })));
        }
        nested_store.items = Some(items);

        // Send the `NestedStore`, represented as a vector of values.
        let res = store
            .write_item(&Item {
                key: key.to_string(),
                value: Some(Box::new(Value::Store(nested_store))),
            })
            .await;
        match res? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // Each entry in this list is a null value in the store.
    for key in config.write_null.into_iter() {
        match store.write_item(&Item { key: key.to_string(), value: None }).await? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the
    // referenced bug has been resolved, we can remove the sleep.
    thread::sleep(time::Duration::from_secs(2));
    Ok(())
}

Server

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use {
    anyhow::{Context as _, Error},
    fidl_examples_keyvaluestore_supporttrees::{
        Item, StoreRequest, StoreRequestStream, Value, WriteError,
    },
    fuchsia_component::server::ServiceFs,
    futures::prelude::*,
    lazy_static::lazy_static,
    regex::Regex,
    std::cell::RefCell,
    std::collections::hash_map::Entry,
    std::collections::HashMap,
    std::str::from_utf8,
};

lazy_static! {
    static ref KEY_VALIDATION_REGEX: Regex =
        Regex::new(r"^[A-Za-z]\w+[A-Za-z0-9]$").expect("Key validation regex failed to compile");
}

// A representation of a key-value store that can contain an arbitrarily deep nesting of other
// key-value stores.
#[allow(dead_code)] // TODO(https://fxbug.dev/318827209)
enum StoreNode {
    Leaf(Option<Vec<u8>>),
    Branch(Box<HashMap<String, StoreNode>>),
}

/// Recursive item writer, which takes a `StoreNode` that may not necessarily be the root node, and
/// writes an entry to it.
fn write_item(
    store: &mut HashMap<String, StoreNode>,
    attempt: Item,
    path: &str,
) -> Result<(), WriteError> {
    // Validate the key.
    if !KEY_VALIDATION_REGEX.is_match(attempt.key.as_str()) {
        println!("Write error: INVALID_KEY, For key: {}", attempt.key);
        return Err(WriteError::InvalidKey);
    }

    // Write to the store, validating that the key did not already exist.
    match store.entry(attempt.key) {
        Entry::Occupied(entry) => {
            println!("Write error: ALREADY_EXISTS, For key: {}", entry.key());
            Err(WriteError::AlreadyExists)
        }
        Entry::Vacant(entry) => {
            let key = format!("{}{}", &path, entry.key());
            match attempt.value {
                // Null entries are allowed.
                None => {
                    println!("Wrote value: NONE at key: {}", key);
                    entry.insert(StoreNode::Leaf(None));
                }
                Some(value) => match *value {
                    // If this is a nested store, recursively make a new store to insert at this
                    // position.
                    Value::Store(entry_list) => {
                        // Validate the value - absent stores, items lists with no children, or any
                        // of the elements within that list being empty boxes, are all not allowed.
                        if entry_list.items.is_some() {
                            let items = entry_list.items.unwrap();
                            if !items.is_empty() && items.iter().all(|i| i.is_some()) {
                                let nested_path = format!("{}/", key);
                                let mut nested_store = HashMap::<String, StoreNode>::new();
                                for item in items.into_iter() {
                                    write_item(&mut nested_store, *item.unwrap(), &nested_path)?;
                                }

                                println!("Created branch at key: {}", key);
                                entry.insert(StoreNode::Branch(Box::new(nested_store)));
                                return Ok(());
                            }
                        }

                        println!("Write error: INVALID_VALUE, For key: {}", key);
                        return Err(WriteError::InvalidValue);
                    }

                    // This is a simple leaf node on this branch.
                    Value::Bytes(value) => {
                        // Validate the value.
                        if value.is_empty() {
                            println!("Write error: INVALID_VALUE, For key: {}", key);
                            return Err(WriteError::InvalidValue);
                        }

                        println!("Wrote key: {}, value: {:?}", key, from_utf8(&value).unwrap());
                        entry.insert(StoreNode::Leaf(Some(value)));
                    }
                },
            }
            Ok(())
        }
    }
}

/// Creates a new instance of the server. Each server has its own bespoke, per-connection instance
/// of the key-value store.
async fn run_server(stream: StoreRequestStream) -> Result<(), Error> {
    // Create a new in-memory key-value store. The store will live for the lifetime of the
    // connection between the server and this particular client.
    let store = RefCell::new(HashMap::<String, StoreNode>::new());

    // Serve all requests on the protocol sequentially - a new request is not handled until its
    // predecessor has been processed.
    stream
        .map(|result| result.context("failed request"))
        .try_for_each(|request| async {
            // Match based on the method being invoked.
            match request {
                StoreRequest::WriteItem { attempt, responder } => {
                    println!("WriteItem request received");

                    // The `responder` parameter is a special struct that manages the outgoing reply
                    // to this method call. Calling `send` on the responder exactly once will send
                    // the reply.
                    responder
                        .send(write_item(&mut store.borrow_mut(), attempt, ""))
                        .context("error sending reply")?;
                    println!("WriteItem response sent");
                }
                StoreRequest::_UnknownMethod { ordinal, .. } => {
                    println!("Received an unknown method with ordinal {ordinal}");
                }
            }
            Ok(())
        })
        .await
}

// A helper enum that allows us to treat a `Store` service instance as a value.
enum IncomingService {
    Store(StoreRequestStream),
}

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Add a discoverable instance of our `Store` protocol - this will allow the client to see the
    // server and connect to it.
    let mut fs = ServiceFs::new_local();
    fs.dir("svc").add_fidl_service(IncomingService::Store);
    fs.take_and_serve_directory_handle()?;
    println!("Listening for incoming connections");

    // The maximum number of concurrent clients that may be served by this process.
    const MAX_CONCURRENT: usize = 10;

    // Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit.
    fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Store(stream)| {
        run_server(stream).unwrap_or_else(|e| println!("{:?}", e))
    })
    .await;

    Ok(())
}

C++ (Natural)

Client

// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.

Server

// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.

C++ (Wire)

Client

// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.

Server

// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.

HLCPP

Client

// TODO(https://fxbug.dev/42060656): HLCPP implementation.

Server

// TODO(https://fxbug.dev/42060656): HLCPP implementation.

fi-0157: Client/server end constraint must be a protocol

The first constraint applied to either client_end or server_end must point to a protocol definition:

library test.bad.fi0157;

type MyStruct = struct {};
alias ServerEnd = server_end:MyStruct;

Change the constraint to point to a protocol instead:

library test.good.fi0157;

protocol MyProtocol {};
alias ServerEnd = server_end:MyProtocol;

fi-0158: Cannot bound twice

An alias declaration cannot change the value of an already set constraint on the type it is aliasing:

library test.bad.fi0158;

alias ByteVec256 = vector<uint8>:256;
alias ByteVec512 = ByteVec256:512;

Instead, the unbounded definition should receive its own alias declaration, and each further constrained alias should inherit from it in turn:

library test.good.fi0158;

alias AliasOfVectorOfString = vector<string>;
alias AliasOfVectorOfStringSmall = AliasOfVectorOfString:8;
alias AliasOfVectorOfStringLarge = AliasOfVectorOfString:16;

This is disallowed to avoid confusion and compiler implementation complexity.

fi-0159: Struct cannot be optional

Structs cannot have the optional constraint:

library test.bad.fi0159;

type Date = struct {
    year uint16;
    month uint8;
    day uint8;
};

type Person = struct {
    name string;
    birthday Date:optional;
};

Change T:optional to box<T> to fix the issue:

library test.good.fi0159;

type Date = struct {
    year uint16;
    month uint8;
    day uint8;
};

type Person = struct {
    name string;
    birthday box<Date>;
};

Only FIDL types that can be made optional with no change to the wire shape are allowed to use the optional constraint. See the optionality guide for more information.

fi-0160: Type cannot be marked as optional twice

This error occurs when a type is made optional twice. Usually, this occurs when the type is marked optional at both its use and declaration sites.

library test.bad.fi0160;

alias MyAlias = vector<string>:optional;

type MyStruct = struct {
    my_member MyAlias:optional;
};

To fix this error, only make the type optional once.

For example, you can remove :optional from the use site.

library test.good.fi0160a;

alias MyAlias = vector<string>:optional;

type MyStruct = struct {
    my_member MyAlias;
};

You could also remove :optional from the alias declaration.

library test.good.fi0160b;

alias MyAlias = vector<string>;

type MyStruct = struct {
    my_member MyAlias:optional;
};

fi-0161: Must have non zero size

This error occurs when you try to set an array size constraint to 0. Arrays cannot be zero-sized.

library test.bad.fi0161;

type Person = struct {
    name string;
    nicknames array<string, 0>;
};

To fix this error, change the size constraint to a positive integer.

library test.good.fi0161;

type Person = struct {
    name string;
    nicknames array<string, 5>;
};

fi-0162: Wrong number of layout parameters

Certain FIDL layouts, like vector and array, take parameters. This error indicates that the highlighted type has an incorrect number of parameters specified:

library test.bad.fi0162a;

type Foo = struct {
    bar array<8>;
};

It can also appear in cases where a non-parameterizable type has erroneously had parameters attached to it:

library test.bad.fi0162b;

type Foo = struct {
    bar uint8<8>;
};

The fix is always to specify the correct number of parameters for the layout in question:

library test.good.fi0162;

type Foo = struct {
    bar array<uint8, 8>;
};

The only parameterized types in FIDL are array<T, N>, box<T>, and vector<T>. The client_end and server_end types used to be parameterized in an older version of the FIDL syntax, but this is no longer the case, though it is a frequent source of run-ins with this error. These two types now take their protocol specification as a (required) constraint instead.

Parameters, which are always listed inside of angle brackets <...>, have some similarities to constraints, which appear after the :... character at the end of the type. For example, at first blush, it may appear odd that array<T, N> specifies its size as a parameter, while vector<T>:N specifies its size as a constraint. The difference is that parameters always affect the wire layout shape of the type in question, while constraints merely alter the set of values that are considered acceptable at encode/decode time for that type, with no effect on wire layout.

See RFC-0050: FIDL Syntax Revamp for a more thorough discussion of the distinction between the two concepts.

fi-0163: Multiple constraint definitions

This error occurs when you try to define multiple constraint definitions using more than one colon (:). Multiple constraint definitions must use the angled bracket syntax type:<constraint1, constraint2, etc>.

library test.bad.fi0163;

type Person = struct {
  name string;
  favorite_color string:30:optional;
};

To fix this error, use the angle bracket syntax for constraints:

library test.good.fi0163;

type Person = struct {
    name string;
    favorite_color string:<30, optional>;
};

fi-0164: Too many constraints

This error occurs when you try to add more constraints to a type than are supported. string, for example, supports at most two constraints.

library test.bad.fi0164;

type Person = struct {
    name string:<0, optional, 20>;
};

To fix this issue, remove the extra constraints:

library test.good.fi0164;

type Person = struct {
    name string:<20, optional>;
};

fi-0165: Expected type

This error occurs when you use a constant or protocol identifier when FIDL is expecting a type.

library test.bad.fi0165;

type Person = struct {
    name string;
    nicknames vector<5>;
};

To fix this error, update your code to use a valid type:

library test.good.fi0165;

type Person = struct {
    name string;
    nicknames vector<string>:5;
};

Protocols are not considered FIDL types and also cannot be used where a type is expected.

fi-0166: Unexpected constraint

This error occurs when you try to use a constraint where it's not expected. Typically, this is due to a named const in the wrong position.

library test.bad.fi0166;

const MIN_SIZE uint8 = 1;
const MAX_SIZE uint8 = 5;

type Person = struct {
    name string;
    nicknames vector<string>:<MIN_SIZE, MAX_SIZE>;
};

To fix this error, remove the constraint:

library test.good.fi0166;

const MAX_SIZE uint8 = 5;

type Person = struct {
    name string;
    nicknames vector<string>:<MAX_SIZE>;
};

fi-0167: Cannot constrain twice

Re-assigning the transport bound for a client_end or server_end that has already had a transport bound defined through an alias declaration is prohibited:

library test.bad.fi0167;

protocol MyOtherProtocol {};

alias ClientEnd = client_end:MyProtocol;
alias ServerEnd = server_end:MyProtocol;

protocol MyProtocol {
    MyMethod(resource struct {
        my_client ClientEnd:MyOtherProtocol;
    }) -> (resource struct {
        my_server ServerEnd:MyOtherProtocol;
    });
};

Instead, aliasing of client_end and server_end types should be avoided entirely:

library test.good.fi0167;

protocol MyProtocol {
    MyMethod(resource struct {
        my_client client_end:MyProtocol;
    }) -> (resource struct {
        my_server server_end:MyProtocol;
    });
};

This is disallowed to avoid confusion and compiler implementation complexity.

fi-0168: Client/server end must have protocol constraint

The first constraint applied to either client_end or server_end must point to a protocol definition:

library test.bad.fi0168;

protocol MyProtocol {
    MyMethod(resource struct {
        server server_end;
    });
};

Add a constraint pointing to the desired protocol:

library test.good.fi0168;

protocol MyProtocol {
    MyMethod(resource struct {
        server server_end:MyProtocol;
    });
};

fi-0169: Boxed type cannot be optional

A type of the form box<T> cannot have the optional constraint applied to it:

library test.bad.fi0169;

type Color = struct {
    red byte;
    green byte;
    blue byte;
};

type MyStruct = struct {
    maybe_color box<Color>:optional;
};

Boxed types are optional by definition, so adding the extra constraint is unnecessary and redundant:

library test.good.fi0169;

type Color = struct {
    red byte;
    green byte;
    blue byte;
};

type MyStruct = struct {
    maybe_color box<Color>;
};

fi-0170

fi-0171: Boxed type should use optional constraint instead

Only a type using the struct layout can be boxed; union, vector, string, client_end, server_end, and zx.Handle must use the optional constraint instead:

library test.bad.fi0171;

using zx;

type MyStruct = resource struct {
    my_resource_member box<zx.Handle>;
};

Convert box<T> to T:optional to fix this issue:

library test.good.fi0171;

using zx;

type MyStruct = resource struct {
    my_resource_member zx.Handle:optional;
};

Only FIDL types that can be made optional with no change to the wire shape are allowed to use the optional constraint. See the optionality guide, or the expandable below, for more information.

FIDL recipe: Optionality

Certain FIDL types can be made optional with no change to the wire shape of their containing message with the addition of the :optional constraint. Further, the table layout is always optional, while the struct layout never is. To make a struct optional, it must be wrapped in a box<T>, thereby changing the wire shape of its containing message.

Base type Optional version Does optionality change the wire layout?
struct {...} box<struct {...}> Yes
table {...} table {...} No
union {...} union {...}:optional No
vector<T> vector<T>:optional No
string string:optional No
zx.Handle zx.Handle:optional No
client_end:P client_end:<P, optional> No
server_end:P server_end:<P, optional> No

All other types (bits, enum, array<T, N>, and the primitive types) cannot be made optional.

In this variant, we allow our key-value store to take other key-value stores as members. In short, we turn it into a tree. We do this by replacing the original definition of value with one that utilizes a two-member union: one variant stores leaf nodes using the same vector<byte> type as before, while the other stores branch nodes in the form of other nested stores.

Reasoning

Here, we see several uses of optionality, whereby we can declare a type that may or may not exist. There are three flavors of optionality in FIDL:

  • Types that have are always stored out-of-line on the wire, and thus have a builtin way to describe "absentness" via the null envelope. Enabling optionality for these types doesn't affect the wire shape of messages they are included in - it simply changes which values are valid for that particular type. The union, vector<T>, client_end, server_end, and zx.Handle types can all be made optional via the addition of the :optional constraint. By making our value union optional, we are able to introduce a canonical "null" entry, in the form of an absent value. This means that empty bytes and absent/empty store properties are invalid values.
  • Unlike the aforementioned types, the struct layout has no extra space where a null header can be stored. Because of this, it needs to be wrapped in an envelope, changing the on-the-wire shape of the message it is being included in. To ensure that this wire-modifying effect easily legible, the Item struct type must be wrapped in a box<T> type template.
  • Finally, table layouts are always optional. An absent table is simply one with none of its members set.

Trees are a naturally self-referential data structure: any node in the tree may contain a leaf with pure data (in this case, a string), or a sub-tree with more nodes. This requires recursion: the definition of Item is now transitively dependent on itself! Representing recursive types in FIDL can be a bit tricky, especially because support is currently somewhat limited. We can support such types as long as there is at least one optional type in the cycle created by the self-reference. For instance, here we define the items struct member to be a box<Item>, thereby breaking the includes cycle.

These changes also make heavy use of anonymous types, or types whose declarations are inlined at their sole point of use, rather than being named, top-level type declarations of their own. By default, the names of anonymous types in the generated language bindings are taken from their local context. For instance, the newly introduced flexible union takes on its owning member's name Value, the newly introduced struct would become Store, and so on. Because this heuristic can sometimes cause collisions, FIDL provides an escape hatch by allowing the author to manually override an anonymous type's generated name. This is done via the @generated_name attribute, which allows one to change the name generated by backends. We can use one here, where the would-be Store type is renamed to NestedStore to prevent a name collision with the protocol declaration that uses that same name.

Implementation

The FIDL, CML, and realm interface definitions are modified as follows:

FIDL

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
library examples.keyvaluestore.supporttrees;

/// An item in the store. The key must match the regex `^[A-z][A-z0-9_\.\/]{2,62}[A-z0-9]$`. That
/// is, it must start with a letter, end with a letter or number, contain only letters, numbers,
/// periods, and slashes, and be between 4 and 64 characters long.
type Item = struct {
    key string:128;
    value strict union {
        // Keep the original `bytes` as one of the options in the new union. All leaf nodes in the
        // tree must be `bytes`, or absent unions (representing empty). Empty byte arrays are
        // disallowed.
        1: bytes vector<byte>:64000;

        // Allows a store within a store, thereby turning our flat key-value store into a tree
        // thereof. Note the use of `@generated_name` to prevent a type-name collision with the
        // `Store` protocol below, and the use of `box<T>` to ensure that there is a break in the
        // chain of recursion, thereby allowing `Item` to include itself in its own definition.
        //
        // This is a table so that added fields, like for example a `hash`, can be easily added in
        // the future.
        2: store @generated_name("nested_store") table {
            1: items vector<box<Item>>;
        };
    }:optional;
};

/// An enumeration of things that may go wrong when trying to write a value to our store.
type WriteError = flexible enum {
    UNKNOWN = 0;
    INVALID_KEY = 1;
    INVALID_VALUE = 2;
    ALREADY_EXISTS = 3;
};

/// A very basic key-value store.
@discoverable
open protocol Store {
    /// Writes an item to the store.
    flexible WriteItem(struct {
        attempt Item;
    }) -> () error WriteError;
};

CML

Client

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/client_bin",
    },
    use: [
        { protocol: "examples.keyvaluestore.supporttrees.Store" },
    ],
    config: {
        write_items: {
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
        },

        // A newline separated list nested entries. The first line should be the key
        // for the nested store, and each subsequent entry should be a pointer to a text file
        // containing the string value. The name of that text file (without the `.txt` suffix) will
        // serve as the entries key.
        write_nested: {
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
        },

        // A list of keys, all of which will be populated as null entries.
        write_null: {
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
        },

    },
}

Server

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/server_bin",
    },
    capabilities: [
        { protocol: "examples.keyvaluestore.supporttrees.Store" },
    ],
    expose: [
        {
            protocol: "examples.keyvaluestore.supporttrees.Store",
            from: "self",
        },
    ],
}

Realm

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    children: [
        {
            name: "client",
            url: "#meta/client.cm",
        },
        {
            name: "server",
            url: "#meta/server.cm",
        },
    ],
    offer: [
        // Route the protocol under test from the server to the client.
        {
            protocol: "examples.keyvaluestore.supporttrees.Store",
            from: "#server",
            to: "#client",
        },

        // Route diagnostics support to all children.
        {
            protocol: [
                "fuchsia.inspect.InspectSink",
                "fuchsia.logger.LogSink",
            ],
            from: "parent",
            to: [
                "#client",
                "#server",
            ],
        },
    ],
}

Client and server implementations can then be written in any supported language:

Rust

Client

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use {
    anyhow::{Context as _, Error},
    config::Config,
    fidl_examples_keyvaluestore_supporttrees::{Item, NestedStore, StoreMarker, Value},
    fuchsia_component::client::connect_to_protocol,
    std::{thread, time},
};

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Load the structured config values passed to this component at startup.
    let config = Config::take_from_startup_handle();

    // Use the Component Framework runtime to connect to the newly spun up server component. We wrap
    // our retained client end in a proxy object that lets us asynchronously send `Store` requests
    // across the channel.
    let store = connect_to_protocol::<StoreMarker>()?;
    println!("Outgoing connection enabled");

    // This client's structured config has one parameter, a vector of strings. Each string is the
    // path to a resource file whose filename is a key and whose contents are a value. We iterate
    // over them and try to write each key-value pair to the remote store.
    for key in config.write_items.into_iter() {
        let path = format!("/pkg/data/{}.txt", key);
        let value = std::fs::read_to_string(path.clone())
            .with_context(|| format!("Failed to load {path}"))?;
        let res = store
            .write_item(&Item {
                key: key.clone(),
                value: Some(Box::new(Value::Bytes(value.into_bytes()))),
            })
            .await;
        match res? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // Add nested entries to the key-value store as well. The entries are strings, where the first
    // line is the key of the entry, and each subsequent entry should be a pointer to a text file
    // containing the string value. The name of that text file (without the `.txt` suffix) will
    // serve as the entries key.
    for spec in config.write_nested.into_iter() {
        let mut items = vec![];
        let mut nested_store = NestedStore::default();
        let mut lines = spec.split("\n");
        let key = lines.next().unwrap();

        // For each entry, make a new entry in the `NestedStore` being built.
        for entry in lines {
            let path = format!("/pkg/data/{}.txt", entry);
            let contents = std::fs::read_to_string(path.clone())
                .with_context(|| format!("Failed to load {path}"))?;
            items.push(Some(Box::new(Item {
                key: entry.to_string(),
                value: Some(Box::new(Value::Bytes(contents.into()))),
            })));
        }
        nested_store.items = Some(items);

        // Send the `NestedStore`, represented as a vector of values.
        let res = store
            .write_item(&Item {
                key: key.to_string(),
                value: Some(Box::new(Value::Store(nested_store))),
            })
            .await;
        match res? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // Each entry in this list is a null value in the store.
    for key in config.write_null.into_iter() {
        match store.write_item(&Item { key: key.to_string(), value: None }).await? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the
    // referenced bug has been resolved, we can remove the sleep.
    thread::sleep(time::Duration::from_secs(2));
    Ok(())
}

Server

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use {
    anyhow::{Context as _, Error},
    fidl_examples_keyvaluestore_supporttrees::{
        Item, StoreRequest, StoreRequestStream, Value, WriteError,
    },
    fuchsia_component::server::ServiceFs,
    futures::prelude::*,
    lazy_static::lazy_static,
    regex::Regex,
    std::cell::RefCell,
    std::collections::hash_map::Entry,
    std::collections::HashMap,
    std::str::from_utf8,
};

lazy_static! {
    static ref KEY_VALIDATION_REGEX: Regex =
        Regex::new(r"^[A-Za-z]\w+[A-Za-z0-9]$").expect("Key validation regex failed to compile");
}

// A representation of a key-value store that can contain an arbitrarily deep nesting of other
// key-value stores.
#[allow(dead_code)] // TODO(https://fxbug.dev/318827209)
enum StoreNode {
    Leaf(Option<Vec<u8>>),
    Branch(Box<HashMap<String, StoreNode>>),
}

/// Recursive item writer, which takes a `StoreNode` that may not necessarily be the root node, and
/// writes an entry to it.
fn write_item(
    store: &mut HashMap<String, StoreNode>,
    attempt: Item,
    path: &str,
) -> Result<(), WriteError> {
    // Validate the key.
    if !KEY_VALIDATION_REGEX.is_match(attempt.key.as_str()) {
        println!("Write error: INVALID_KEY, For key: {}", attempt.key);
        return Err(WriteError::InvalidKey);
    }

    // Write to the store, validating that the key did not already exist.
    match store.entry(attempt.key) {
        Entry::Occupied(entry) => {
            println!("Write error: ALREADY_EXISTS, For key: {}", entry.key());
            Err(WriteError::AlreadyExists)
        }
        Entry::Vacant(entry) => {
            let key = format!("{}{}", &path, entry.key());
            match attempt.value {
                // Null entries are allowed.
                None => {
                    println!("Wrote value: NONE at key: {}", key);
                    entry.insert(StoreNode::Leaf(None));
                }
                Some(value) => match *value {
                    // If this is a nested store, recursively make a new store to insert at this
                    // position.
                    Value::Store(entry_list) => {
                        // Validate the value - absent stores, items lists with no children, or any
                        // of the elements within that list being empty boxes, are all not allowed.
                        if entry_list.items.is_some() {
                            let items = entry_list.items.unwrap();
                            if !items.is_empty() && items.iter().all(|i| i.is_some()) {
                                let nested_path = format!("{}/", key);
                                let mut nested_store = HashMap::<String, StoreNode>::new();
                                for item in items.into_iter() {
                                    write_item(&mut nested_store, *item.unwrap(), &nested_path)?;
                                }

                                println!("Created branch at key: {}", key);
                                entry.insert(StoreNode::Branch(Box::new(nested_store)));
                                return Ok(());
                            }
                        }

                        println!("Write error: INVALID_VALUE, For key: {}", key);
                        return Err(WriteError::InvalidValue);
                    }

                    // This is a simple leaf node on this branch.
                    Value::Bytes(value) => {
                        // Validate the value.
                        if value.is_empty() {
                            println!("Write error: INVALID_VALUE, For key: {}", key);
                            return Err(WriteError::InvalidValue);
                        }

                        println!("Wrote key: {}, value: {:?}", key, from_utf8(&value).unwrap());
                        entry.insert(StoreNode::Leaf(Some(value)));
                    }
                },
            }
            Ok(())
        }
    }
}

/// Creates a new instance of the server. Each server has its own bespoke, per-connection instance
/// of the key-value store.
async fn run_server(stream: StoreRequestStream) -> Result<(), Error> {
    // Create a new in-memory key-value store. The store will live for the lifetime of the
    // connection between the server and this particular client.
    let store = RefCell::new(HashMap::<String, StoreNode>::new());

    // Serve all requests on the protocol sequentially - a new request is not handled until its
    // predecessor has been processed.
    stream
        .map(|result| result.context("failed request"))
        .try_for_each(|request| async {
            // Match based on the method being invoked.
            match request {
                StoreRequest::WriteItem { attempt, responder } => {
                    println!("WriteItem request received");

                    // The `responder` parameter is a special struct that manages the outgoing reply
                    // to this method call. Calling `send` on the responder exactly once will send
                    // the reply.
                    responder
                        .send(write_item(&mut store.borrow_mut(), attempt, ""))
                        .context("error sending reply")?;
                    println!("WriteItem response sent");
                }
                StoreRequest::_UnknownMethod { ordinal, .. } => {
                    println!("Received an unknown method with ordinal {ordinal}");
                }
            }
            Ok(())
        })
        .await
}

// A helper enum that allows us to treat a `Store` service instance as a value.
enum IncomingService {
    Store(StoreRequestStream),
}

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Add a discoverable instance of our `Store` protocol - this will allow the client to see the
    // server and connect to it.
    let mut fs = ServiceFs::new_local();
    fs.dir("svc").add_fidl_service(IncomingService::Store);
    fs.take_and_serve_directory_handle()?;
    println!("Listening for incoming connections");

    // The maximum number of concurrent clients that may be served by this process.
    const MAX_CONCURRENT: usize = 10;

    // Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit.
    fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Store(stream)| {
        run_server(stream).unwrap_or_else(|e| println!("{:?}", e))
    })
    .await;

    Ok(())
}

C++ (Natural)

Client

// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.

Server

// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.

C++ (Wire)

Client

// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.

Server

// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.

HLCPP

Client

// TODO(https://fxbug.dev/42060656): HLCPP implementation.

Server

// TODO(https://fxbug.dev/42060656): HLCPP implementation.

fi-0172: Resource definition must use uint32 subtype

The subtype of a resource_definition declaration must be uint32:

library test.bad.fi0172;

type MySubtype = strict enum : uint32 {
    NONE = 0;
};

resource_definition MyResource : uint8 {
    properties {
        subtype MySubtype;
    };
};

Change the subtype to an uint32 to fix this error:

library test.good.fi0172;

type MySubtype = strict enum : uint32 {
    NONE = 0;
};

resource_definition MyResource : uint32 {
    properties {
        subtype MySubtype;
    };
};

This is an error related to FIDL's internal implementation, and thus should only ever be surfaced to developers working on FIDL's core libraries. End users should never see this error.

The resource_definition declaration it refers to is FIDL's internal means of defining resources like handles, and is likely to change in the future as part of the handle generalization effort.

fi-0173: Resource definition must specify subtype

The resource_definition declaration cannot omit the subtype member:

library test.bad.fi0173;

resource_definition MyResource : uint32 {
    properties {
        rights uint32;
    };
};

Point this member to a valid enum : uint32 declaration:

library test.good.fi0173;

resource_definition MyResource : uint32 {
    properties {
        subtype flexible enum : uint32 {};
        rights uint32;
    };
};

This is an error related to FIDL's internal implementation, and thus should only ever be surfaced to developers working on FIDL's core libraries. End users should never see this error.

The resource_definition declaration it refers to is FIDL's internal means of defining resources like handles, and is likely to change in the future as part of the handle generalization effort.

fi-0174

fi-0175: Resource definition subtype property must refer to enum

The resource_definition declaration cannot use a non-enum as the subtype member:

library test.bad.fi0175;

resource_definition MyResource : uint32 {
    properties {
        subtype struct {};
    };
};

Point this member to a valid enum : uint32 declaration:

library test.good.fi0175;

resource_definition MyResource : uint32 {
    properties {
        subtype flexible enum : uint32 {};
    };
};

This is an error related to FIDL's internal implementation, and thus should only ever be surfaced to developers working on FIDL's core libraries. End users should never see this error.

The resource_definition declaration it refers to is FIDL's internal means of defining resources like handles, and is likely to change in the future as part of the handle generalization effort.

fi-0176

fi-0177: Resource definition rights property must refer to bits

The resource_definition declaration cannot use a non-bits as the rights member:

library test.bad.fi0177;

type MySubtype = enum : uint32 {
    NONE = 0;
    VMO = 3;
};

resource_definition MyResource : uint32 {
    properties {
        subtype MySubtype;
        rights string;
    };
};

Point this member to a valid bits : uint32 declaration:

library test.good.fi0177;

type MySubtype = enum : uint32 {
    NONE = 0;
    VMO = 3;
};

resource_definition MyResource : uint32 {
    properties {
        subtype MySubtype;
        rights uint32;
    };
};

This is an error related to FIDL's internal implementation, and thus should only ever be surfaced to developers working on FIDL's core libraries. End users should never see this error.

The resource_definition declaration it refers to is FIDL's internal means of defining resources like handles, and is likely to change in the future as part of the handle generalization effort.

fi-0178: Unused import

Not referencing a dependency imported via the using declaration is an error:

library test.bad.fi0178;

using dependent;

type Foo = struct {
    does_not int64;
    use_dependent int32;
};

Make sure all such imports are used in the library importing, either by actually referencing the import, or removing the unused dependency:

library test.good.fi0178;

using dependent;

type Foo = struct {
    dep dependent.Bar;
};

fi-0179: Newtypes cannot be constrained

Newtypes from RFC-0052: Type aliasing and new types are not allowed to be constrained. For example, a newtype of string cannot be constrained with :optional:

library test.bad.fi0179;

type Name = string;

type Info = struct {
    name Name:optional;
};

In this situation, we can make make the name field optional by putting it in a table rather than a struct:

library test.good.fi0179;

type Name = string;

type Info = table {
    1: name Name;
};

This restriction simplifies the design of newtypes. It's not clear what the API and ABI should look like for a constrained newtype in general (e.g., should the constraints apply to the newtype itself, or flow through to the underlying type?).

fi-0180: Zircon C types are experimental

The built-in types usize, uintptr, uchar, and experimental_pointer are being developed for the Zither project. They cannot be used in ordinary FIDL libraries:

library test.bad.fi0180;

type Data = struct {
    size usize64;
};

Instead, use a different type, for example uint64 instead of usize:

library test.good.fi0180;

type Data = struct {
    size uint64;
};

fi-0181: Library attribute argument references constant

Attribute arguments on library declarations are not allowed to reference constants:

@custom_attribute(VALUE)
library test.bad.fi0181a;

const VALUE string = "hello";

Instead, provide a literal argument:

@custom_attribute("hello")
library test.good.fi0181a;

This restriction exists because it is rarely needed and supporting it adds unwarranted complexity to the compiler.

fi-0182: Legacy only applies to removed elements

An element that is never removed (directly or via inheritance) is not allowed to provide the legacy argument to the @available attribute:

@available(added=1)
library test.bad.fi0182;

protocol Foo {
    @available(added=2, legacy=true)
    Bar() -> ();
};

Instead, remove the legacy argument:

@available(added=1)
library test.good.fi0182a;

protocol Foo {
    @available(added=2)
    Bar() -> ();
};

Alternatively, keep the legacy argument but add the removed argument:

@available(added=1)
library test.good.fi0182b;

protocol Foo {
    @available(added=2, removed=3, legacy=true)
    Bar() -> ();
};

The legacy argument indicates whether an element should be added back at the LEGACY version after being removed, so it doesn't make sense to use it on an element that is never removed. See the documentation on legacy for more details.

fi-0183: Legacy conflicts with parent

A child element cannot be marked legacy=true if its parent is removed without legacy support:

@available(added=1)
library test.bad.fi0183;

@available(removed=3)
protocol Foo {
    @available(added=2, legacy=true)
    Bar() -> ();
};

Instead, remove the legacy=true argument:

@available(added=1)
library test.good.fi0183a;

@available(removed=3)
protocol Foo {
    @available(added=2)
    Bar() -> ();
};

Alternatively, add legacy=true to the parent as well. The child will inherit it, so it's not necessary to specify it again there. Note that this will also affect any other children the parent has.

@available(added=1)
library test.good.fi0183b;

@available(removed=3, legacy=true)
protocol Foo {
    @available(added=2)
    Bar() -> ();
};

The legacy=true argument indicates an element should be added back at the LEGACY version after being removed, and a child element cannot exist independently of its parent. See the documentation on legacy for more details.

fi-0184: Unexpected control character

String literals are not allowed to contain raw control characters (ASCII characters from 0x00 to 0x1f):

library test.bad.fi0184;

const TAB string = "	"; // literal tab character

Instead, use an escape sequence. In this case, \t is the right one:

library test.good.fi0184a;

const TAB string = "\t";

Alternatively, you can use a Unicode escape sequence. This works for any Unicode code point:

library test.good.fi0184b;

const TAB string = "\u{9}";

Raw control characters are not allowed in string literals because they are all either whitespace or non-printable, so they would be confusing and hard to notice if embedded directly in the FIDL source file.

fi-0185: Unicode escape sequence missing braces

Unicode escape sequences in string literals must specify a code point in braces:

library test.bad.fi0185;

const SMILE string = "\u";

To fix the error, specify a code point in braces:

library test.good.fi0185;

const SMILE string = "\u{1F600}";

fi-0186: Unterminated Unicode escape sequence

Unicode escape sequences in string literals must be terminated:

library test.bad.fi0186;

const SMILE string = "\u{1F600";

To terminate the escape sequence, add a closing brace }:

library test.good.fi0186;

const SMILE string = "\u{1F600}";

fi-0187: Empty Unicode escape sequence

Unicode escape sequences in string literals must have at least one hex digit:

library test.bad.fi0187;

const SMILE string = "\u{}";

To fix the error, add hex digits to specify a Unicode code point:

library test.good.fi0187;

const SMILE string = "\u{1F600}";

fi-0188: Too many digits in Unicode escape sequence

Unicode escape sequences in string literals cannot have more than 6 hex digits:

library test.bad.fi0188;

const SMILE string = "\u{001F600}";

To fix the error, specify at most 6 hex digits. In this case, there are leading zeros that we can remove:

library test.good.fi0188;

const SMILE string = "\u{1F600}";

This restriction exists because all valid Unicode code points fit in 6 hex digits, so there is no reason to allow more digits than that.

fi-0189: Unicode code point too large

Unicode escape sequences in string literals cannot specify a Unicode code point greater than the maximum of 0x10ffff:

library test.bad.fi0189;

const TOO_LARGE string = "\u{110000}";

Instead, make sure the code point is valid:

library test.good.fi0189;

const MAX_CODEPOINT string = "\u{10ffff}";

fi-0190

fi-0191: Method must specify strictness

This error indicates that a FIDL method does not have a strict or flexible modifier.

library test.bad.fi0191;

open protocol Example {
    OneWay();
};

To fix this, add either strict or flexible to the method. If this is an existing method, you must use strict and should see the compatibility guide for guidance on changing it to flexible. If this is a new method, you should see the API rubric for guidance on which to choose.

library test.good.fi0191;

open protocol Example {
    flexible OneWay();
};

FIDL is currently undergoing a migration in order to support handling unknown interactions, defined in RFC-0138. This new feature allows the modifiers strict and flexible to apply to FIDL methods and events. Historically, all methods behaved as though they were strict, however at the end of this migration, the default value will be flexible. To avoid confusion and possible issues arising from changing the method default modifier from strict to flexible, the method modifier is required during this transitional period. When the migration is complete, this will be changed from an error to a linter suggestion.

For more information about unknown interactions, see the FIDL language reference.

fi-0192: Protocol must specify openness

This error indicates that a FIDL protocol does not have an open, ajar, or closed modifier.

library test.bad.fi0192;

protocol ImplicitOpenness {};

To fix this, add open, ajar, or closed to the protocol. If this is an existing protocol, you must use closed and should see the compatibility guide for guidance on changing it to open or ajar. If this is a new method, you should see the API rubric for guidance on which to choose.

library test.good.fi0192;

open protocol ImplicitOpenness {};

FIDL is currently undergoing a migration in order to support handling unknown interactions, defined in RFC-0138. This new feature adds three new modifiers, open, ajar, and closed, which apply to FIDL protocols. Historically, all protocols behaved as though they were closed, however at the end of this migration, the default value will be open. To avoid confusion and possible issues arising from changing the protocol default modifier from closed to open, the protocol modifier is required during this transitional period. When the migration is complete, this will be changed from an error to a linter suggestion.

For more information about unknown interactions, see the FIDL language reference.

fi-0193: Cannot box type

Types other than structs cannot be boxed. For example, a primitive type cannot be boxed:

library test.bad.fi0193;

type MyStruct = struct {
    my_member box<bool>;
};

To box a primitive, put it in a single-member struct instead:

library test.good.fi0193;

type MyStruct = struct {
    my_member box<struct {
        my_bool bool;
    }>;
};

Note that some types can be made optional via the use of the optional constraint. See the optionality guide, or the expandable below, for more information.

FIDL recipe: Optionality

Certain FIDL types can be made optional with no change to the wire shape of their containing message with the addition of the :optional constraint. Further, the table layout is always optional, while the struct layout never is. To make a struct optional, it must be wrapped in a box<T>, thereby changing the wire shape of its containing message.

Base type Optional version Does optionality change the wire layout?
struct {...} box<struct {...}> Yes
table {...} table {...} No
union {...} union {...}:optional No
vector<T> vector<T>:optional No
string string:optional No
zx.Handle zx.Handle:optional No
client_end:P client_end:<P, optional> No
server_end:P server_end:<P, optional> No

All other types (bits, enum, array<T, N>, and the primitive types) cannot be made optional.

In this variant, we allow our key-value store to take other key-value stores as members. In short, we turn it into a tree. We do this by replacing the original definition of value with one that utilizes a two-member union: one variant stores leaf nodes using the same vector<byte> type as before, while the other stores branch nodes in the form of other nested stores.

Reasoning

Here, we see several uses of optionality, whereby we can declare a type that may or may not exist. There are three flavors of optionality in FIDL:

  • Types that have are always stored out-of-line on the wire, and thus have a builtin way to describe "absentness" via the null envelope. Enabling optionality for these types doesn't affect the wire shape of messages they are included in - it simply changes which values are valid for that particular type. The union, vector<T>, client_end, server_end, and zx.Handle types can all be made optional via the addition of the :optional constraint. By making our value union optional, we are able to introduce a canonical "null" entry, in the form of an absent value. This means that empty bytes and absent/empty store properties are invalid values.
  • Unlike the aforementioned types, the struct layout has no extra space where a null header can be stored. Because of this, it needs to be wrapped in an envelope, changing the on-the-wire shape of the message it is being included in. To ensure that this wire-modifying effect easily legible, the Item struct type must be wrapped in a box<T> type template.
  • Finally, table layouts are always optional. An absent table is simply one with none of its members set.

Trees are a naturally self-referential data structure: any node in the tree may contain a leaf with pure data (in this case, a string), or a sub-tree with more nodes. This requires recursion: the definition of Item is now transitively dependent on itself! Representing recursive types in FIDL can be a bit tricky, especially because support is currently somewhat limited. We can support such types as long as there is at least one optional type in the cycle created by the self-reference. For instance, here we define the items struct member to be a box<Item>, thereby breaking the includes cycle.

These changes also make heavy use of anonymous types, or types whose declarations are inlined at their sole point of use, rather than being named, top-level type declarations of their own. By default, the names of anonymous types in the generated language bindings are taken from their local context. For instance, the newly introduced flexible union takes on its owning member's name Value, the newly introduced struct would become Store, and so on. Because this heuristic can sometimes cause collisions, FIDL provides an escape hatch by allowing the author to manually override an anonymous type's generated name. This is done via the @generated_name attribute, which allows one to change the name generated by backends. We can use one here, where the would-be Store type is renamed to NestedStore to prevent a name collision with the protocol declaration that uses that same name.

Implementation

The FIDL, CML, and realm interface definitions are modified as follows:

FIDL

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
library examples.keyvaluestore.supporttrees;

/// An item in the store. The key must match the regex `^[A-z][A-z0-9_\.\/]{2,62}[A-z0-9]$`. That
/// is, it must start with a letter, end with a letter or number, contain only letters, numbers,
/// periods, and slashes, and be between 4 and 64 characters long.
type Item = struct {
    key string:128;
    value strict union {
        // Keep the original `bytes` as one of the options in the new union. All leaf nodes in the
        // tree must be `bytes`, or absent unions (representing empty). Empty byte arrays are
        // disallowed.
        1: bytes vector<byte>:64000;

        // Allows a store within a store, thereby turning our flat key-value store into a tree
        // thereof. Note the use of `@generated_name` to prevent a type-name collision with the
        // `Store` protocol below, and the use of `box<T>` to ensure that there is a break in the
        // chain of recursion, thereby allowing `Item` to include itself in its own definition.
        //
        // This is a table so that added fields, like for example a `hash`, can be easily added in
        // the future.
        2: store @generated_name("nested_store") table {
            1: items vector<box<Item>>;
        };
    }:optional;
};

/// An enumeration of things that may go wrong when trying to write a value to our store.
type WriteError = flexible enum {
    UNKNOWN = 0;
    INVALID_KEY = 1;
    INVALID_VALUE = 2;
    ALREADY_EXISTS = 3;
};

/// A very basic key-value store.
@discoverable
open protocol Store {
    /// Writes an item to the store.
    flexible WriteItem(struct {
        attempt Item;
    }) -> () error WriteError;
};

CML

Client

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/client_bin",
    },
    use: [
        { protocol: "examples.keyvaluestore.supporttrees.Store" },
    ],
    config: {
        write_items: {
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
        },

        // A newline separated list nested entries. The first line should be the key
        // for the nested store, and each subsequent entry should be a pointer to a text file
        // containing the string value. The name of that text file (without the `.txt` suffix) will
        // serve as the entries key.
        write_nested: {
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
        },

        // A list of keys, all of which will be populated as null entries.
        write_null: {
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
        },

    },
}

Server

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/server_bin",
    },
    capabilities: [
        { protocol: "examples.keyvaluestore.supporttrees.Store" },
    ],
    expose: [
        {
            protocol: "examples.keyvaluestore.supporttrees.Store",
            from: "self",
        },
    ],
}

Realm

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    children: [
        {
            name: "client",
            url: "#meta/client.cm",
        },
        {
            name: "server",
            url: "#meta/server.cm",
        },
    ],
    offer: [
        // Route the protocol under test from the server to the client.
        {
            protocol: "examples.keyvaluestore.supporttrees.Store",
            from: "#server",
            to: "#client",
        },

        // Route diagnostics support to all children.
        {
            protocol: [
                "fuchsia.inspect.InspectSink",
                "fuchsia.logger.LogSink",
            ],
            from: "parent",
            to: [
                "#client",
                "#server",
            ],
        },
    ],
}

Client and server implementations can then be written in any supported language:

Rust

Client

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use {
    anyhow::{Context as _, Error},
    config::Config,
    fidl_examples_keyvaluestore_supporttrees::{Item, NestedStore, StoreMarker, Value},
    fuchsia_component::client::connect_to_protocol,
    std::{thread, time},
};

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Load the structured config values passed to this component at startup.
    let config = Config::take_from_startup_handle();

    // Use the Component Framework runtime to connect to the newly spun up server component. We wrap
    // our retained client end in a proxy object that lets us asynchronously send `Store` requests
    // across the channel.
    let store = connect_to_protocol::<StoreMarker>()?;
    println!("Outgoing connection enabled");

    // This client's structured config has one parameter, a vector of strings. Each string is the
    // path to a resource file whose filename is a key and whose contents are a value. We iterate
    // over them and try to write each key-value pair to the remote store.
    for key in config.write_items.into_iter() {
        let path = format!("/pkg/data/{}.txt", key);
        let value = std::fs::read_to_string(path.clone())
            .with_context(|| format!("Failed to load {path}"))?;
        let res = store
            .write_item(&Item {
                key: key.clone(),
                value: Some(Box::new(Value::Bytes(value.into_bytes()))),
            })
            .await;
        match res? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // Add nested entries to the key-value store as well. The entries are strings, where the first
    // line is the key of the entry, and each subsequent entry should be a pointer to a text file
    // containing the string value. The name of that text file (without the `.txt` suffix) will
    // serve as the entries key.
    for spec in config.write_nested.into_iter() {
        let mut items = vec![];
        let mut nested_store = NestedStore::default();
        let mut lines = spec.split("\n");
        let key = lines.next().unwrap();

        // For each entry, make a new entry in the `NestedStore` being built.
        for entry in lines {
            let path = format!("/pkg/data/{}.txt", entry);
            let contents = std::fs::read_to_string(path.clone())
                .with_context(|| format!("Failed to load {path}"))?;
            items.push(Some(Box::new(Item {
                key: entry.to_string(),
                value: Some(Box::new(Value::Bytes(contents.into()))),
            })));
        }
        nested_store.items = Some(items);

        // Send the `NestedStore`, represented as a vector of values.
        let res = store
            .write_item(&Item {
                key: key.to_string(),
                value: Some(Box::new(Value::Store(nested_store))),
            })
            .await;
        match res? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // Each entry in this list is a null value in the store.
    for key in config.write_null.into_iter() {
        match store.write_item(&Item { key: key.to_string(), value: None }).await? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the
    // referenced bug has been resolved, we can remove the sleep.
    thread::sleep(time::Duration::from_secs(2));
    Ok(())
}

Server

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use {
    anyhow::{Context as _, Error},
    fidl_examples_keyvaluestore_supporttrees::{
        Item, StoreRequest, StoreRequestStream, Value, WriteError,
    },
    fuchsia_component::server::ServiceFs,
    futures::prelude::*,
    lazy_static::lazy_static,
    regex::Regex,
    std::cell::RefCell,
    std::collections::hash_map::Entry,
    std::collections::HashMap,
    std::str::from_utf8,
};

lazy_static! {
    static ref KEY_VALIDATION_REGEX: Regex =
        Regex::new(r"^[A-Za-z]\w+[A-Za-z0-9]$").expect("Key validation regex failed to compile");
}

// A representation of a key-value store that can contain an arbitrarily deep nesting of other
// key-value stores.
#[allow(dead_code)] // TODO(https://fxbug.dev/318827209)
enum StoreNode {
    Leaf(Option<Vec<u8>>),
    Branch(Box<HashMap<String, StoreNode>>),
}

/// Recursive item writer, which takes a `StoreNode` that may not necessarily be the root node, and
/// writes an entry to it.
fn write_item(
    store: &mut HashMap<String, StoreNode>,
    attempt: Item,
    path: &str,
) -> Result<(), WriteError> {
    // Validate the key.
    if !KEY_VALIDATION_REGEX.is_match(attempt.key.as_str()) {
        println!("Write error: INVALID_KEY, For key: {}", attempt.key);
        return Err(WriteError::InvalidKey);
    }

    // Write to the store, validating that the key did not already exist.
    match store.entry(attempt.key) {
        Entry::Occupied(entry) => {
            println!("Write error: ALREADY_EXISTS, For key: {}", entry.key());
            Err(WriteError::AlreadyExists)
        }
        Entry::Vacant(entry) => {
            let key = format!("{}{}", &path, entry.key());
            match attempt.value {
                // Null entries are allowed.
                None => {
                    println!("Wrote value: NONE at key: {}", key);
                    entry.insert(StoreNode::Leaf(None));
                }
                Some(value) => match *value {
                    // If this is a nested store, recursively make a new store to insert at this
                    // position.
                    Value::Store(entry_list) => {
                        // Validate the value - absent stores, items lists with no children, or any
                        // of the elements within that list being empty boxes, are all not allowed.
                        if entry_list.items.is_some() {
                            let items = entry_list.items.unwrap();
                            if !items.is_empty() && items.iter().all(|i| i.is_some()) {
                                let nested_path = format!("{}/", key);
                                let mut nested_store = HashMap::<String, StoreNode>::new();
                                for item in items.into_iter() {
                                    write_item(&mut nested_store, *item.unwrap(), &nested_path)?;
                                }

                                println!("Created branch at key: {}", key);
                                entry.insert(StoreNode::Branch(Box::new(nested_store)));
                                return Ok(());
                            }
                        }

                        println!("Write error: INVALID_VALUE, For key: {}", key);
                        return Err(WriteError::InvalidValue);
                    }

                    // This is a simple leaf node on this branch.
                    Value::Bytes(value) => {
                        // Validate the value.
                        if value.is_empty() {
                            println!("Write error: INVALID_VALUE, For key: {}", key);
                            return Err(WriteError::InvalidValue);
                        }

                        println!("Wrote key: {}, value: {:?}", key, from_utf8(&value).unwrap());
                        entry.insert(StoreNode::Leaf(Some(value)));
                    }
                },
            }
            Ok(())
        }
    }
}

/// Creates a new instance of the server. Each server has its own bespoke, per-connection instance
/// of the key-value store.
async fn run_server(stream: StoreRequestStream) -> Result<(), Error> {
    // Create a new in-memory key-value store. The store will live for the lifetime of the
    // connection between the server and this particular client.
    let store = RefCell::new(HashMap::<String, StoreNode>::new());

    // Serve all requests on the protocol sequentially - a new request is not handled until its
    // predecessor has been processed.
    stream
        .map(|result| result.context("failed request"))
        .try_for_each(|request| async {
            // Match based on the method being invoked.
            match request {
                StoreRequest::WriteItem { attempt, responder } => {
                    println!("WriteItem request received");

                    // The `responder` parameter is a special struct that manages the outgoing reply
                    // to this method call. Calling `send` on the responder exactly once will send
                    // the reply.
                    responder
                        .send(write_item(&mut store.borrow_mut(), attempt, ""))
                        .context("error sending reply")?;
                    println!("WriteItem response sent");
                }
                StoreRequest::_UnknownMethod { ordinal, .. } => {
                    println!("Received an unknown method with ordinal {ordinal}");
                }
            }
            Ok(())
        })
        .await
}

// A helper enum that allows us to treat a `Store` service instance as a value.
enum IncomingService {
    Store(StoreRequestStream),
}

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Add a discoverable instance of our `Store` protocol - this will allow the client to see the
    // server and connect to it.
    let mut fs = ServiceFs::new_local();
    fs.dir("svc").add_fidl_service(IncomingService::Store);
    fs.take_and_serve_directory_handle()?;
    println!("Listening for incoming connections");

    // The maximum number of concurrent clients that may be served by this process.
    const MAX_CONCURRENT: usize = 10;

    // Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit.
    fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Store(stream)| {
        run_server(stream).unwrap_or_else(|e| println!("{:?}", e))
    })
    .await;

    Ok(())
}

C++ (Natural)

Client

// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.

Server

// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.

C++ (Wire)

Client

// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.

Server

// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.

HLCPP

Client

// TODO(https://fxbug.dev/42060656): HLCPP implementation.

Server

// TODO(https://fxbug.dev/42060656): HLCPP implementation.

fi-0194

fi-0195

fi-0196

fi-0201: Platform version not selected

This error occurs when you compile a versioned FIDL library without choosing a version:

// fidlc --files test.fidl --out test.json
@available(platform="foo", added=1)
library test.bad.fi0201;

To fix it, choose a version with the --available command line flag:

// fidlc --files test.fidl --out test.json --available foo:1
@available(platform="foo", added=1)
library test.good.fi0201;

The version must be a number greater than or equal to 1, or one of the special versions HEAD and LEGACY. For more information, see the documentation for FIDL versioning.

fi-0202

fi-0203: Removed and replaced are mutually exclusive

The @available attribute supports the arguments removed and replaced, but they can't be used together:

@available(added=1)
library test.bad.fi0203;

protocol Foo {
    @available(removed=2, replaced=2)
    Foo();
};

To fix the error, delete one of the arguments. If you intend to remove the element without replacing it, keep removed and delete replaced:

@available(added=1)
library test.good.fi0203a;

open protocol Foo {
    @available(removed=2)
    strict Foo();
};

Alternatively, if you are swapping the element with a new definition, keep replaced and delete removed:

@available(added=1)
library test.good.fi0203b;

open protocol Foo {
    @available(replaced=2)
    strict Foo();

    @available(added=2)
    flexible Foo();
};

It doesn't make sense to use removed and replaced together because they have opposite meanings. When an element is marked removed, fidlc validates that there IS NOT a replacement element added at the same version. When an element is marked replaced, fidlc validates that there IS a replacement element added at the same version.

See FIDL versioning to learn more about versioning.

fi-0204: Library cannot be replaced

The @available attribute's replaced argument cannot be used on a library declaration:

@available(added=1, replaced=2)
library test.bad.fi0204;

Instead, use the removed argument:

@available(added=1, removed=2)
library test.good.fi0204;

The replaced argument indicates that an element is replaced by a new definition. This is not supported for an entire library, since we assume each library only has one set of files that defines it.

See FIDL versioning to learn more about versioning.

fi-0205: Removed element must not have a replacement

When an element is marked @available(removed=N), it indicates that the element can no longer be used at version N. It should not merely be replaced by a new definition marked @available(added=N):

@available(added=1)
library test.bad.fi0204;

open protocol Foo {
    @available(removed=2)
    strict Bar();
    @available(added=2)
    flexible Bar();
};

If the replacement is intentional, make it explicit by using the replaced argument instead of the removed argument:

@available(added=1)
library test.good.fi0204a;

open protocol Foo {
    @available(replaced=2)
    strict Bar();
    @available(added=2)
    flexible Bar();
};

If the replacement is unintentional, remove or rename the other element:

@available(added=1)
library test.good.fi0204b;

open protocol Foo {
    @available(removed=2)
    strict Bar();
    @available(added=2)
    flexible NewBar();
};

The removed and replaced arguments both have the same effect on the generated bindings, but they represent different points in API lifecycle. The removed argument indicates the end of an API, while the replaced argument indicates a transition to a new definition.

See FIDL versioning to learn more about versioning.

fi-0206: Replaced element has no replacement

When an element is marked @available(replaced=N), it indicates that the element is replaced by a new definition marked @available(added=N). The FIDL compiler will report an error if it cannot find such a definition:

@available(added=1)
library test.bad.fi0205;

open protocol Foo {
    @available(replaced=2)
    strict Bar();
};

If you did not intend to replace the element, use the removed argument instead of the replaced argument:

@available(added=1)
library test.good.fi0205a;

open protocol Foo {
    @available(removed=2)
    strict Bar();
};

If you intended to replace the element, add a replacement definition:

@available(added=1)
library test.good.fi0205b;

open protocol Foo {
    @available(replaced=2)
    strict Bar();
    @available(added=2)
    flexible Bar();
};

The removed and replaced arguments both have the same effect on the generated bindings, but they represent different points in API lifecycle. The removed argument indicates the end of an API, while the replaced argument indicates a transition to a new definition.

See FIDL versioning to learn more about versioning.

fi-0207: Type shape integer overflow

A FIDL type must not be so large that its size overflows uint32:

library test.bad.fi0207;

type Foo = struct {
    bytes array<uint64, 536870912>;
};

To fix the error, use a smaller array size:

library test.good.fi0207;

type Foo = struct {
    bytes array<uint64, 100>;
};

In practice FIDL types should be far smaller than 232 bytes, because they are usually sent over Zircon channels, which are limited to 64 KiB per message.

fi-0208: Reserved platform

Certain platform names are reserved by FIDL. For example, the "unversioned" platform is reserved to represent libraries that don't use versioning:

@available(platform="unversioned", added=1)
library test.bad.fi0208;

Choose a different platform name instead:

@available(platform="foo", added=1)
library test.good.fi0208;

See FIDL versioning to learn more about versioning.