blob: d31404567106485dcaf9b3e5d7739d13793d3d9a [file] [log] [blame]
// 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