| // Copyright 2021 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. |
| |
| // ============================================================================ |
| // This is an accompanying example code for the C++ async response tutorial. |
| // Head over there for the full walk-through: |
| // https://fuchsia.dev/fuchsia-src/development/languages/fidl/tutorials/cpp/basics/domain-objects |
| // ============================================================================ |
| |
| #include <type_traits> |
| |
| #include <gtest/gtest.h> |
| |
| // [START include] |
| #include <fidl/fuchsia.examples/cpp/fidl.h> |
| // [END include] |
| |
| namespace { |
| |
| // |
| // Examples of using the natural types. |
| // |
| |
| // Verify that the wire types are available. |
| using WireFileMode = fuchsia_examples::wire::FileMode; |
| using ProtocolMarker = fuchsia_examples::Echo; |
| |
| // [START natural-bits] |
| TEST(NaturalTypes, Bits) { |
| // Bits implement bitwise operators such as |, ~, &, ^. |
| auto flags = ~fuchsia_examples::FileMode::kRead & fuchsia_examples::FileMode::kExecute; |
| flags = fuchsia_examples::FileMode::kRead | fuchsia_examples::FileMode::kWrite; |
| |
| // Bits implement the set difference operation (clearing bits) under -. |
| ASSERT_EQ(flags - fuchsia_examples::FileMode::kRead, fuchsia_examples::FileMode::kWrite); |
| flags -= fuchsia_examples::FileMode::kRead; |
| ASSERT_EQ(flags, fuchsia_examples::FileMode::kWrite); |
| |
| // Bits may be explicitly casted to their underlying integer type. |
| flags = fuchsia_examples::FileMode::kRead | fuchsia_examples::FileMode::kWrite; |
| ASSERT_EQ(static_cast<uint16_t>(flags), 0b11); |
| |
| // They may also be explicitly constructed from an underlying type, but |
| // this may result in invalid values for strict bits. |
| flags = fuchsia_examples::FileMode(0b11); |
| |
| // A safer alternative is |TryFrom|, which constructs an instance of |
| // |FileMode| only if underlying primitive does not contain any unknown |
| // members that is not defined in the FIDL schema. Otherwise, returns |
| // |std::nullopt|. |
| std::optional<fuchsia_examples::FileMode> maybe_flags = |
| fuchsia_examples::FileMode::TryFrom(0b1111); |
| ASSERT_FALSE(maybe_flags.has_value()); |
| |
| // Another alternative is |TruncatingUnknown| which clears any bits not |
| // defined in the FIDL schema. |
| fuchsia_examples::FileMode truncated_flags = |
| fuchsia_examples::FileMode::TruncatingUnknown(0b1111); |
| ASSERT_EQ(truncated_flags, fuchsia_examples::FileMode(0b111)); |
| |
| // Bits implement bitwise-assignment. |
| flags |= fuchsia_examples::FileMode::kExecute; |
| |
| // They also support equality and expose a |kMask| that is the |
| // bitwise OR of all defined bit members. |
| ASSERT_EQ(flags, fuchsia_examples::FileMode::kMask); |
| |
| // A flexible bits type additionally supports querying the unknown bits. |
| fuchsia_examples::FlexibleFileMode flexible_flags = fuchsia_examples::FlexibleFileMode(0b1111); |
| ASSERT_TRUE(flexible_flags.has_unknown_bits()); |
| ASSERT_EQ(static_cast<uint16_t>(flexible_flags.unknown_bits()), 0b1000); |
| } |
| // [END natural-bits] |
| |
| // [START natural-enums] |
| TEST(NaturalTypes, Enums) { |
| // Enums members are scoped constants under the enum type. |
| fuchsia_examples::LocationType location = fuchsia_examples::LocationType::kAirport; |
| |
| // They may be explicitly casted to their underlying type. |
| ASSERT_EQ(static_cast<uint32_t>(fuchsia_examples::LocationType::kMuseum), 1u); |
| |
| // They may also be casted to their underlying type without specifying the precise type. |
| uint32_t strict_underlying = fidl::ToUnderlying(fuchsia_examples::LocationType::kMuseum); |
| ASSERT_EQ(strict_underlying, 1u); |
| |
| // Enums support switch case statements. |
| // A strict enum can be switched exhaustively. |
| (void)[=] { |
| switch (location) { |
| case fuchsia_examples::LocationType::kAirport: |
| return 1; |
| case fuchsia_examples::LocationType::kMuseum: |
| return 2; |
| case fuchsia_examples::LocationType::kRestaurant: |
| return 3; |
| } |
| }; |
| |
| // A flexible enum requires a `default:` case. |
| fuchsia_examples::FlexibleLocationType flexible_location = |
| fuchsia_examples::FlexibleLocationType::kAirport; |
| (void)[=] { |
| switch (flexible_location) { |
| case fuchsia_examples::FlexibleLocationType::kAirport: |
| return 1; |
| case fuchsia_examples::FlexibleLocationType::kMuseum: |
| return 2; |
| case fuchsia_examples::FlexibleLocationType::kRestaurant: |
| return 3; |
| default: // Removing this branch will fail to compile. |
| return 4; |
| } |
| }; |
| |
| // A flexible enum also supports asking if the current enum value was |
| // not known in the FIDL schema, or marked with `@unknown`. |
| ASSERT_FALSE(flexible_location.IsUnknown()); |
| |
| // Strict enums may be uninitialized. Their value will be undefined. |
| fuchsia_examples::LocationType strict_location; |
| (void)strict_location; |
| |
| // Flexible enums may be default initialized. They will either contain |
| // the member marked with `@unknown` in the FIDL schema if exists, |
| // or a compiler-reserved unknown value otherwise. |
| fuchsia_examples::FlexibleLocationType default_flexible_location; |
| ASSERT_TRUE(default_flexible_location.IsUnknown()); |
| } |
| // [END natural-enums] |
| |
| // [START natural-structs] |
| TEST(NaturalTypes, Structs) { |
| // Structs may be default constructed with fields set to default values, |
| // provided that all fields are also default constructible. |
| fuchsia_examples::Color default_color; |
| ASSERT_EQ(default_color.id(), 0u); |
| ASSERT_EQ(default_color.name(), "red"); |
| |
| // They support constructing by supplying fields in a sequence. |
| fuchsia_examples::Color blue = {1, "blue"}; |
| ASSERT_EQ(blue.id(), 1u); |
| |
| // They also support a more readable syntax that names individual fields, |
| // similar to C++ designated initialization. The double brace (`{{`) syntax |
| // is necessary to workaround C++ limitations on aggregate initialization. |
| fuchsia_examples::Color red{{.id = 2, .name = "red"}}; |
| ASSERT_EQ(red.id(), 2u); |
| fuchsia_examples::Color designated_1 = {{.id = 1, .name = "designated"}}; |
| ASSERT_EQ(designated_1.id(), 1u); |
| fuchsia_examples::Color designated_2{{.id = 2, .name = "designated"}}; |
| ASSERT_EQ(designated_2.id(), 2u); |
| |
| // Setters take the value to be set as argument. |
| fuchsia_examples::Color color; |
| color.id(100); |
| color.name("green"); |
| ASSERT_EQ(color.id(), 100u); |
| ASSERT_EQ(color.name(), "green"); |
| |
| // Setters may also be chained. |
| color.id(42).name("yellow"); |
| ASSERT_EQ(color.id(), 42u); |
| ASSERT_EQ(color.name(), "yellow"); |
| |
| // Equality is implemented for value types. |
| ASSERT_EQ(color, fuchsia_examples::Color(42, "yellow")); |
| |
| // Copies and moves. |
| fuchsia_examples::Color color_copy{color}; |
| ASSERT_EQ(color_copy.name(), "yellow"); |
| fuchsia_examples::Color color_moved{std::move(color)}; |
| ASSERT_EQ(color_moved.name(), "yellow"); |
| // The state of |color| is now unspecified. |
| } |
| // [END natural-structs] |
| |
| // [START natural-unions] |
| TEST(NaturalTypes, Unions) { |
| // Factory functions are used to construct natural union objects. |
| // To construct a union whose active member is |int_value|, use |WithIntValue|. |
| auto int_val = fuchsia_examples::JsonValue::WithIntValue(1); |
| |
| // |Which| obtains an enum corresponding to the active member, which may be |
| // used in switch cases. |
| ASSERT_EQ(int_val.Which(), fuchsia_examples::JsonValue::Tag::kIntValue); |
| |
| // When directly accessing a field, one must first check if the field is |
| // active before dereferencing it. |
| ASSERT_TRUE(int_val.int_value().has_value()); |
| ASSERT_TRUE(static_cast<bool>(int_val.int_value())); |
| ASSERT_EQ(int_val.int_value().value(), 1); |
| |
| // Another example, this time activating the |string_value| member. |
| auto str_val = fuchsia_examples::JsonValue::WithStringValue("1"); |
| ASSERT_EQ(str_val.Which(), fuchsia_examples::JsonValue::Tag::kStringValue); |
| ASSERT_TRUE(str_val.string_value().has_value()); |
| |
| // Unions are not default constructible, to avoid invalid states. |
| static_assert(!std::is_default_constructible_v<fuchsia_examples::JsonValue>, |
| "Unions cannot be default constructed"); |
| |
| fuchsia_examples::JsonValue value = fuchsia_examples::JsonValue::WithStringValue("hello"); |
| ASSERT_FALSE(value.int_value()); |
| ASSERT_TRUE(value.string_value()); |
| |
| // |value_or| returns a fallback if the corresponding member is not active. |
| ASSERT_EQ(value.int_value().value_or(42), 42); |
| |
| // Setters take the value to be set as argument. |
| // Setting a field causes that field to become the active member. |
| value.int_value(2); |
| ASSERT_TRUE(value.int_value()); |
| ASSERT_FALSE(value.string_value()); |
| |
| // |take| invokes the move operation on the member if it is active. |
| value.string_value("foo"); |
| std::optional<std::string> str = value.string_value().take(); |
| ASSERT_TRUE(str.has_value()); |
| ASSERT_EQ(str.value(), "foo"); |
| |
| // Equality is implemented for value types. |
| value.string_value("bar"); |
| ASSERT_EQ(value, fuchsia_examples::JsonValue::WithStringValue("bar")); |
| |
| // Copies and moves. |
| fuchsia_examples::JsonValue value_copy{value}; |
| ASSERT_EQ(value.string_value().value(), "bar"); |
| fuchsia_examples::JsonValue value_moved{std::move(value)}; |
| ASSERT_EQ(value_moved.string_value().value(), "bar"); |
| |
| // When switching over the tag from a flexible union, one must add a `default:` |
| // case, to handle members not understood by the FIDL schema or to handle |
| // newly added members in a source compatible way. |
| fuchsia_examples::FlexibleJsonValue flexible_value = |
| fuchsia_examples::FlexibleJsonValue::WithIntValue(1); |
| switch (flexible_value.Which()) { |
| case fuchsia_examples::FlexibleJsonValue::Tag::kIntValue: |
| ASSERT_EQ(flexible_value.int_value().value(), 1); |
| break; |
| case fuchsia_examples::FlexibleJsonValue::Tag::kStringValue: |
| FAIL() << "Unexpected tag. |flexible_value| was set to int"; |
| break; |
| default: // Removing this branch will fail to compile. |
| break; |
| } |
| } |
| // [END natural-unions] |
| |
| // [START natural-tables] |
| TEST(NaturalTypes, Tables) { |
| // A default constructed table is empty. That is, every field is absent. |
| fuchsia_examples::User user; |
| ASSERT_TRUE(user.IsEmpty()); |
| |
| // Each accessor returns a |std::optional<T>|, where |T| is the field type. |
| ASSERT_FALSE(user.age().has_value()); |
| |
| // Setters take the value to be set as argument. |
| user.age(100); |
| user.age(*user.age() + 100); |
| ASSERT_EQ(user.age().value(), 200); |
| |
| // Setters may also be chained. |
| user.name("foo").age(30); |
| ASSERT_EQ(user.name().value(), "foo"); |
| ASSERT_EQ(user.age().value(), 30); |
| |
| // Since each field is an |std::optional<T>|, they may also be cleared. |
| user.name().reset(); |
| ASSERT_FALSE(user.name().has_value()); |
| |
| // Assigning an |std::nullopt| also clears the field. |
| user.name("bar"); |
| ASSERT_TRUE(user.name().has_value()); |
| user.name() = std::nullopt; |
| ASSERT_FALSE(user.name().has_value()); |
| |
| // |value_or| returns a fallback if the corresponding field is absent. |
| ASSERT_EQ(user.name().value_or("anonymous"), "anonymous"); |
| user.age().reset(); |
| ASSERT_TRUE(user.IsEmpty()); |
| |
| // Similar to structs, tables support constructing by naming individual fields. |
| // Fields that are omitted from the designated initialization syntax will be |
| // absent from the table. |
| user = {{.age = 100, .name = "foo"}}; |
| ASSERT_TRUE(user.age()); |
| ASSERT_TRUE(user.name()); |
| |
| user = {{.age = 100}}; |
| ASSERT_TRUE(user.age()); |
| ASSERT_FALSE(user.name()); |
| |
| // Equality is implemented for value types. |
| ASSERT_EQ(user, fuchsia_examples::User{{.age = 100}}); |
| |
| // Copies and moves. |
| fuchsia_examples::User user_copy{user}; |
| ASSERT_EQ(*user.age(), 100); |
| fuchsia_examples::User user_moved{std::move(user)}; |
| ASSERT_EQ(*user_moved.age(), 100); |
| } |
| // [END natural-tables] |
| |
| // |
| // Examples of using the wire types. |
| // |
| |
| // [START wire-bits] |
| TEST(WireTypes, Bits) { |
| static_assert(std::is_same<fuchsia_examples::FileMode, fuchsia_examples::wire::FileMode>::value, |
| "natural bits should be equivalent to wire bits"); |
| static_assert(fuchsia_examples::FileMode::kMask == fuchsia_examples::wire::FileMode::kMask, |
| "natural bits should be equivalent to wire bits"); |
| |
| using fuchsia_examples::wire::FileMode; |
| auto flags = FileMode::kRead | FileMode::kWrite | FileMode::kExecute; |
| ASSERT_EQ(flags, FileMode::kMask); |
| } |
| // [END wire-bits] |
| |
| // [START wire-enums] |
| TEST(WireTypes, Enums) { |
| static_assert( |
| std::is_same<fuchsia_examples::LocationType, fuchsia_examples::wire::LocationType>::value, |
| "natural enums should be equivalent to wire enums"); |
| |
| ASSERT_EQ(static_cast<uint32_t>(fuchsia_examples::wire::LocationType::kMuseum), 1u); |
| } |
| // [END wire-enums] |
| |
| // [START wire-structs] |
| TEST(WireTypes, Structs) { |
| // Wire structs are simple C++ structs with all their member fields declared |
| // public. One may invoke aggregate initialization: |
| fuchsia_examples::wire::Color blue = {1, "blue"}; |
| ASSERT_EQ(blue.id, 1u); |
| ASSERT_EQ(blue.name.get(), "blue"); |
| |
| // ..or designated initialization. |
| fuchsia_examples::wire::Color blue_designated = {.id = 1, .name = "blue"}; |
| ASSERT_EQ(blue_designated.id, 1u); |
| ASSERT_EQ(blue_designated.name.get(), "blue"); |
| |
| // A wire struct may be default constructed, but user-defined default values |
| // are not supported. |
| // Default-initializing a struct means all fields are zero-initialized. |
| fuchsia_examples::wire::Color default_color; |
| ASSERT_EQ(default_color.id, 0u); |
| ASSERT_TRUE(default_color.name.is_null()); |
| ASSERT_TRUE(default_color.name.empty()); |
| |
| // There are no getters/setters. One simply reads or mutates the member field. |
| blue.id = 2; |
| ASSERT_EQ(blue.id, 2u); |
| |
| // Here we demonstrate that wire structs do not own their out-of-line children. |
| // Copying a struct will not copy their out-of-line children. Pointers are |
| // simply aliased. |
| { |
| fuchsia_examples::wire::Color blue2 = blue; |
| ASSERT_EQ(blue2.name.data(), blue.name.data()); |
| } |
| // Similarly, destroying a wire struct object does not destroy out-of-line |
| // children. Destroying |blue2| does not invalidate the string contents in |name|. |
| ASSERT_EQ(blue.name.get(), "blue"); |
| } |
| // [END wire-structs] |
| |
| // [START wire-unions] |
| TEST(WireTypes, Unions) { |
| // When the active member is larger than 4 bytes, it is stored out-of-line, |
| // and the union will borrow the out-of-line content. The lifetimes can be |
| // tricky to reason about, hence the FIDL runtime provides a |fidl::AnyArena| |
| // interface for arena-based allocation of members. The built-in |
| // implementation is |fidl::Arena|. |
| // |
| // Pass the arena as the first argument to |With...| factory functions, to |
| // construct the member content on the arena, and have the union reference it. |
| fidl::Arena arena; |
| fuchsia_examples::wire::JsonValue str_union = |
| fuchsia_examples::wire::JsonValue::WithStringValue(arena, "1"); |
| |
| // |Which| obtains an enum corresponding to the active member, which may be |
| // used in switch cases. |
| ASSERT_EQ(str_union.Which(), fuchsia_examples::wire::JsonValue::Tag::kStringValue); |
| |
| // Before accessing the |string_value| member, one should check if the union |
| // indeed currently holds this member, by querying |is_string_value|. |
| // Accessing the wrong member will cause a panic. |
| ASSERT_TRUE(str_union.is_string_value()); |
| ASSERT_EQ("1", str_union.string_value().get()); |
| |
| // When the active member is smaller or equal to 4 bytes, such as an |
| // |int32_t| here, the entire member is inlined into the union object. |
| // In these cases, arena allocation is not necessary, and the union |
| // object wholly owns the member. |
| fuchsia_examples::wire::JsonValue int_union = fuchsia_examples::wire::JsonValue::WithIntValue(1); |
| ASSERT_TRUE(int_union.is_int_value()); |
| ASSERT_EQ(1, int_union.int_value()); |
| |
| // A default constructed wire union is invalid. |
| // It must be initialized with a valid member before use. |
| // One is not allowed to send invalid unions through FIDL client/server APIs. |
| fuchsia_examples::wire::JsonValue default_union; |
| ASSERT_TRUE(default_union.has_invalid_tag()); |
| default_union = fuchsia_examples::wire::JsonValue::WithStringValue(arena, "hello"); |
| ASSERT_FALSE(default_union.has_invalid_tag()); |
| ASSERT_TRUE(default_union.is_string_value()); |
| ASSERT_EQ(default_union.string_value().get(), "hello"); |
| |
| // Optional unions are represented with |fidl::WireOptional|. |
| fidl::WireOptional<fuchsia_examples::wire::JsonValue> optional_json; |
| ASSERT_FALSE(optional_json.has_value()); |
| optional_json = fuchsia_examples::wire::JsonValue::WithIntValue(42); |
| ASSERT_TRUE(optional_json.has_value()); |
| // |fidl::WireOptional| has a |std::optional|-like API. |
| fuchsia_examples::wire::JsonValue& value = optional_json.value(); |
| ASSERT_TRUE(value.is_int_value()); |
| |
| // When switching over the tag from a flexible union, one must add a `default:` |
| // case, to handle members not understood by the FIDL schema or to handle |
| // newly added members in a source compatible way. |
| fuchsia_examples::wire::FlexibleJsonValue flexible_value = |
| fuchsia_examples::wire::FlexibleJsonValue::WithIntValue(1); |
| switch (flexible_value.Which()) { |
| case fuchsia_examples::wire::FlexibleJsonValue::Tag::kIntValue: |
| ASSERT_EQ(flexible_value.int_value(), 1); |
| break; |
| case fuchsia_examples::wire::FlexibleJsonValue::Tag::kStringValue: |
| FAIL() << "Unexpected tag. |flexible_value| was set to int"; |
| break; |
| default: // Removing this branch will fail to compile. |
| break; |
| } |
| } |
| // [END wire-unions] |
| |
| // [START wire-tables] |
| TEST(WireTypes, Tables) { |
| fidl::Arena arena; |
| // To construct a wire table, you need to first create a corresponding |
| // |Builder| object, which borrows an arena. The |arena| will be used to |
| // allocate the table frame, a bookkeeping structure for field presence. |
| auto builder = fuchsia_examples::wire::User::Builder(arena); |
| |
| // To set a table field, call the member function with the same name on the |
| // builder. The arguments will be forwarded to the field constructor, and the |
| // field is allocated on the initial |arena|. |
| builder.age(10); |
| |
| // Note that only the inline portion of the field is automatically placed in |
| // the arena. The field itself may reference its own out-of-line content, |
| // such as in the case of |name| whose type is |fidl::StringView|. |name| |
| // will reference the "jdoe" literal, which lives in static program storage. |
| builder.name("jdoe"); |
| |
| // Call |Build| to finalize the table builder into a |User| table. |
| // The builder is no longer needed after this point. |user| will continue to |
| // reference objects allocated in the |arena|. |
| fuchsia_examples::wire::User user = builder.Build(); |
| ASSERT_FALSE(user.IsEmpty()); |
| |
| // Before accessing a field, one should check if it is present, by querying |
| // |has_...|. Accessing an absent field will panic. |
| ASSERT_TRUE(user.has_name()); |
| ASSERT_EQ(user.name().get(), "jdoe"); |
| |
| // Setters may be chained, leading to a fluent syntax. |
| user = fuchsia_examples::wire::User::Builder(arena).age(30).name("bob").Build(); |
| ASSERT_FALSE(user.IsEmpty()); |
| ASSERT_TRUE(user.has_age()); |
| ASSERT_EQ(user.age(), 30); |
| ASSERT_TRUE(user.has_name()); |
| ASSERT_EQ(user.name().get(), "bob"); |
| |
| // A default constructed wire table is empty. |
| // This is mostly useful to make requests or replies with empty tables. |
| fuchsia_examples::wire::User defaulted_user; |
| ASSERT_TRUE(defaulted_user.IsEmpty()); |
| |
| // In some situations it could be difficult to provide an arena when |
| // constructing tables. For example, here it is hard to provide constructor |
| // arguments to 10 tables at once. Because a default constructed wire table is |
| // empty, a new table instance should be built and assigned in its place. |
| fidl::Array<fuchsia_examples::wire::User, 10> users; |
| for (auto& user : users) { |
| ASSERT_TRUE(user.IsEmpty()); |
| user = fuchsia_examples::wire::User::Builder(arena).age(30).Build(); |
| ASSERT_FALSE(user.IsEmpty()); |
| ASSERT_EQ(user.age(), 30); |
| } |
| ASSERT_EQ(users[0].age(), 30); |
| |
| // Finally, tables support checking if it was received with unknown fields. |
| // A table created by ourselves will never have unknown fields. |
| ASSERT_FALSE(user.HasUnknownData()); |
| } |
| // [END wire-tables] |
| |
| // |
| // Examples of converting between wire and natural types. |
| // |
| |
| // [START natural-to-wire] |
| TEST(Conversion, NaturalToWire) { |
| // Let's start with a natural table. |
| fuchsia_examples::User user{{.age = 100, .name = "foo"}}; |
| |
| // To convert it to its corresponding wire domain object, we need a |
| // |fidl::AnyArena| implementation to allocate the storage, here an |arena|. |
| fidl::Arena arena; |
| |
| // Call |fidl::ToWire| with the arena and the natural domain object. |
| // All out-of-line fields will live on the |arena|. |
| fuchsia_examples::wire::User wire_user = fidl::ToWire(arena, user); |
| ASSERT_TRUE(wire_user.has_age()); |
| ASSERT_EQ(wire_user.age(), 100); |
| ASSERT_TRUE(wire_user.has_name()); |
| ASSERT_EQ(wire_user.name().get(), "foo"); |
| } |
| // [END natural-to-wire] |
| |
| // [START wire-to-natural] |
| TEST(Conversion, WireToNatural) { |
| fidl::Arena arena; |
| |
| // Let's start with a wire table. |
| fuchsia_examples::wire::User wire_user = |
| fuchsia_examples::wire::User::Builder(arena).age(30).name("bob").Build(); |
| |
| // Call |fidl::ToNatural| with the wire domain object. |
| // All child fields will be owned by |user|. |
| fuchsia_examples::User user = fidl::ToNatural(wire_user); |
| ASSERT_TRUE(user.age().has_value()); |
| ASSERT_EQ(user.age().value(), 30); |
| ASSERT_TRUE(user.name().has_value()); |
| ASSERT_EQ(user.name().value(), "bob"); |
| } |
| // [END wire-to-natural] |
| |
| } // namespace |