Skip to content

Commit

Permalink
Add basic codegen and Encode/Decode support for nullable and optional…
Browse files Browse the repository at this point in the history
… struct/command/event fields (#10891)

* Add basic support for nullable and optional fields.

Affects command fields, event fields, struct fields.

* Add some unit tests for nullable and optional encode/decode

* Add simple optional/nullable end-to-end test.
  • Loading branch information
bzbarsky-apple authored and pull[bot] committed Nov 15, 2021
1 parent 1b2cab3 commit c6d94cd
Show file tree
Hide file tree
Showing 41 changed files with 2,115 additions and 24 deletions.
16 changes: 16 additions & 0 deletions examples/all-clusters-app/all-clusters-common/all-clusters-app.zap
Original file line number Diff line number Diff line change
Expand Up @@ -14875,6 +14875,14 @@
"source": "client",
"incoming": 1,
"outgoing": 0
},
{
"name": "TestNullableOptionalRequest",
"code": 15,
"mfgCode": null,
"source": "client",
"incoming": 1,
"outgoing": 0
}
],
"attributes": [
Expand Down Expand Up @@ -14934,6 +14942,14 @@
"source": "server",
"incoming": 0,
"outgoing": 1
},
{
"name": "TestNullableOptionalResponse",
"code": 6,
"mfgCode": null,
"source": "server",
"incoming": 0,
"outgoing": 1
}
],
"attributes": [
Expand Down
30 changes: 30 additions & 0 deletions examples/chip-tool/commands/common/Command.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
#pragma once

#include "controller/ExampleOperationalCredentialsIssuer.h"
#include <app/data-model/Nullable.h>
#include <controller/CHIPDeviceController.h>
#include <inet/InetInterface.h>
#include <lib/core/Optional.h>
#include <lib/support/Span.h>
#include <lib/support/logging/CHIPLogging.h>

Expand Down Expand Up @@ -151,6 +153,34 @@ class Command
return AddArgument(name, min, max, reinterpret_cast<std::underlying_type_t<T> *>(out));
}

template <typename T>
size_t AddArgument(const char * name, chip::Optional<T> * value)
{
// We always require our args to be provided for the moment.
return AddArgument(name, &value->Emplace());
}

template <typename T>
size_t AddArgument(const char * name, int64_t min, uint64_t max, chip::Optional<T> * value)
{
// We always require our args to be provided for the moment.
return AddArgument(name, min, max, &value->Emplace());
}

template <typename T>
size_t AddArgument(const char * name, chip::app::DataModel::Nullable<T> * value)
{
// We always require our args to be provided for the moment.
return AddArgument(name, &value->SetNonNull());
}

template <typename T>
size_t AddArgument(const char * name, int64_t min, uint64_t max, chip::app::DataModel::Nullable<T> * value)
{
// We always require our args to be provided for the moment.
return AddArgument(name, min, max, &value->SetNonNull());
}

virtual CHIP_ERROR Run() = 0;

private:
Expand Down
12 changes: 12 additions & 0 deletions examples/chip-tool/commands/tests/TestCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,18 @@ class TestCommand : public CHIPCommand

bool CheckValueAsString(const char * itemName, chip::CharSpan current, const char * expected);

template <typename T>
bool CheckValuePresent(const char * itemName, const chip::Optional<T> & value)
{
if (value.HasValue())
{
return true;
}

Exit(std::string(itemName) + " expected to have value but doesn't");
return false;
}

chip::Callback::Callback<chip::Controller::OnDeviceConnected> mOnDeviceConnectedCallback;
chip::Callback::Callback<chip::Controller::OnDeviceConnectionFailure> mOnDeviceConnectionFailureCallback;

Expand Down
2 changes: 2 additions & 0 deletions examples/chip-tool/templates/commands.zapt
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ static void On{{asUpperCamelCase parent.name}}{{asUpperCamelCase name}}Success(v
{{~#*inline "field"}}data.{{asLowerCamelCase label}}{{/inline~}}
{{#if isArray}}
ChipLogProgress(Zcl, " {{label}}: Array printing is not implemented yet.");
{{else if isOptional}}
ChipLogProgress(Zcl, " {{label}}: Optional printing is not implemented yet.");
{{else if (isOctetString type)}}
ChipLogProgress(Zcl, " {{label}}: %zu", {{>field}}.size());
{{else if (isCharString type)}}
Expand Down
10 changes: 9 additions & 1 deletion examples/chip-tool/templates/partials/test_cluster.zapt
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,12 @@ class {{filename}}: public TestCommand
VerifyOrReturn(mReceivedReport_{{waitForReport.index}}, Exit("Initial report not received!"));
{{/if}}
{{#chip_tests_item_response_parameters}}
{{~#*inline "item"}}{{asLowerCamelCase name}}{{/inline}}
{{~#*inline "item"}}{{asLowerCamelCase name}}{{#if isOptional}}.Value(){{/if}}{{/inline}}
{{#if hasExpectedValue}}
{{#if isOptional}}
{{~#*inline "item"}}{{asLowerCamelCase name}}{{/inline}}
VerifyOrReturn(CheckValuePresent("{{> item}}", {{> item}}));
{{/if}}
VerifyOrReturn(CheckValue
{{~#if isList}}AsListLength("{{>item}}", {{>item}}, {{expectedValue.length}})
{{else if isArray}}AsList("{{>item}}", {{>item}}, {{expectedValue}})
Expand All @@ -182,6 +186,10 @@ class {{filename}}: public TestCommand
);
{{/if}}
{{#if hasExpectedConstraints}}
{{#if isOptional}}
{{~#*inline "item"}}{{asLowerCamelCase name}}{{/inline}}
VerifyOrReturn(CheckValuePresent("{{> item}}", {{> item}}));
{{/if}}
{{#if expectedConstraints.type}}VerifyOrReturn(CheckConstraintType("{{>item}}", "", "{{expectedConstraints.type}}"));{{/if}}
{{~#if expectedConstraints.format}}VerifyOrReturn(CheckConstraintFormat("{{>item}}", "", "{{expectedConstraints.format}}"));{{/if}}
{{~#if expectedConstraints.minLength}}VerifyOrReturn(CheckConstraintMinLength("{{>item}}", {{>item}}.size(), {{expectedConstraints.minLength}}));{{/if}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
{{#if isArray}}
{{#if isOptional}}
{{#if ignore}}
{{>commandValue ns=ns container=(concat container ".Emplace()") definedValue=definedValue type=type isOptional=false ignore=true}}
{{else}}
{{>commandValue ns=ns container=(concat container "." label ".Emplace()") definedValue=definedValue type=type isOptional=false ignore=true}}
{{/if}}
{{else if isNullable}}
{{#if ignore}}
{{>commandValue ns=ns container=(concat container ".SetNonNull()") definedValue=definedValue type=type isNullable=false ignore=true}}
{{else}}
{{>commandValue ns=ns container=(concat container "." label ".SetNonNull()") definedValue=definedValue type=type isNullable=false ignore=true}}
{{/if}}
{{else if isArray}}

{{! forceNotList=true because we really want the type of a single item here }}
{{zapTypeToEncodableClusterObjectType type ns=ns forceNotList=true}} {{asLowerCamelCase label}}List[{{definedValue.length}}];
{{! forceNotList=true because we really want the type of a single item here.
Similarly, forceNotOptional=true and forceNotNullable=true because we
have accounted for those already. }}
{{zapTypeToEncodableClusterObjectType type ns=ns forceNotList=true forceNotNullable=true forceNotOptional=true}} {{asLowerCamelCase label}}List[{{definedValue.length}}];
{{#each definedValue}}
{{>commandValue ns=../ns container=(concat (asLowerCamelCase ../label) "List[" @index "]") definedValue=. type=../type ignore=true}}
{{/each}}
Expand Down
24 changes: 24 additions & 0 deletions src/app/clusters/test-cluster-server/test-cluster-server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,30 @@ bool emberAfTestClusterClusterTestEnumsRequestCallback(CommandHandler * commandO
return true;
}

bool emberAfTestClusterClusterTestNullableOptionalRequestCallback(
CommandHandler * commandObj, ConcreteCommandPath const & commandPath,
Commands::TestNullableOptionalRequest::DecodableType const & commandData)
{
Commands::TestNullableOptionalResponse::Type response;
response.wasPresent = commandData.arg1.HasValue();
if (response.wasPresent)
{
bool wasNull = commandData.arg1.Value().IsNull();
response.wasNull.SetValue(wasNull);
if (!wasNull)
{
response.value.SetValue(commandData.arg1.Value().Value());
}
}

CHIP_ERROR err = commandObj->AddResponseData(commandPath, response);
if (err != CHIP_NO_ERROR)
{
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
}
return true;
}

// -----------------------------------------------------------------------------
// Plugin initialization

Expand Down
33 changes: 33 additions & 0 deletions src/app/data-model/Decode.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@

#pragma once

#include <app/data-model/Nullable.h>
#include <lib/core/CHIPError.h>
#include <lib/core/CHIPSafeCasts.h>
#include <lib/core/CHIPTLV.h>
#include <lib/core/Optional.h>

namespace chip {
namespace app {
Expand Down Expand Up @@ -93,6 +95,37 @@ CHIP_ERROR Decode(TLV::TLVReader & reader, X & x)
return x.Decode(reader);
}

/*
* @brief
*
* Decodes an optional value (struct field, command field, event field).
*/
template <typename X>
CHIP_ERROR Decode(TLV::TLVReader & reader, Optional<X> & x)
{
// If we are calling this, it means we found the right tag, so just decode
// the item.
return Decode(reader, x.Emplace());
}

/*
* @brief
*
* Decodes a nullable value.
*/
template <typename X>
CHIP_ERROR Decode(TLV::TLVReader & reader, Nullable<X> & x)
{
if (reader.GetType() == TLV::kTLVType_Null)
{
x.SetNull();
return CHIP_NO_ERROR;
}

// We have a value; decode it.
return Decode(reader, x.SetNonNull());
}

} // namespace DataModel
} // namespace app
} // namespace chip
33 changes: 33 additions & 0 deletions src/app/data-model/Encode.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@

#pragma once

#include <app/data-model/Nullable.h>
#include <lib/core/CHIPTLV.h>
#include <lib/core/Optional.h>

namespace chip {
namespace app {
Expand Down Expand Up @@ -78,6 +80,37 @@ CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag, const X & x)
return x.Encode(writer, tag);
}

/*
* @brief
*
* Encodes an optional value (struct field, command field, event field).
*/
template <typename X>
CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag, const Optional<X> & x)
{
if (x.HasValue())
{
return Encode(writer, tag, x.Value());
}
// If no value, just do nothing.
return CHIP_NO_ERROR;
}

/*
* @brief
*
* Encodes a nullable value.
*/
template <typename X>
CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag, const Nullable<X> & x)
{
if (x.IsNull())
{
return writer.PutNull(tag);
}
return Encode(writer, tag, x.Value());
}

} // namespace DataModel
} // namespace app
} // namespace chip
56 changes: 56 additions & 0 deletions src/app/data-model/Nullable.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
*
* Copyright (c) 2020-2021 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

#include <lib/core/Optional.h>

namespace chip {
namespace app {
namespace DataModel {

/*
* Dedicated type for nullable things, to differentiate them from optional
* things.
*/
template <typename T>
struct Nullable : protected Optional<T>
{
//
// The following 'using' statement is needed to make visible
// all constructors of the base class within this derived class.
//
using Optional<T>::Optional;

// Pull in APIs that make sense on Nullable with the same names as on
// Optional.
using Optional<T>::Value;

constexpr void SetNull() { Optional<T>::ClearValue(); }
constexpr bool IsNull() const { return !Optional<T>::HasValue(); }

template <class... Args>
constexpr T & SetNonNull(Args &&... args)
{
return Optional<T>::Emplace(std::forward<Args>(args)...);
}
};

} // namespace DataModel
} // namespace app
} // namespace chip
Loading

0 comments on commit c6d94cd

Please sign in to comment.