diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/util/KeyValueEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/util/KeyValueEntry.java new file mode 100644 index 0000000000..3e758f69aa --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/util/KeyValueEntry.java @@ -0,0 +1,18 @@ +/** + * Copyright © 2016-2025 The Thingsboard Authors + * + * 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. + */ +package org.thingsboard.server.common.data.util; + +public record KeyValueEntry(K key, V value) {} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 9fb6e451e8..64b4f70b64 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -216,7 +216,7 @@ public class TbHttpClient { if (config.getQueryParams() != null) { processedQueryParams = config.getQueryParams().stream() .map(param -> { - var processedParamName = TbNodeUtils.processPattern(param.name(), msg); + var processedParamName = TbNodeUtils.processPattern(param.key(), msg); var processedParamValue = TbNodeUtils.processPattern(param.value(), msg); return new QueryParam(processedParamName, processedParamValue); }) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java index ac4d493819..bbef69ff31 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java @@ -15,7 +15,8 @@ */ package org.thingsboard.rule.engine.rest; -import jakarta.validation.Valid; +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.NotNull; import lombok.Data; import org.springframework.http.HttpHeaders; @@ -23,10 +24,12 @@ import org.springframework.http.MediaType; import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.credentials.AnonymousCredentials; import org.thingsboard.rule.engine.credentials.ClientCredentials; +import org.thingsboard.server.common.data.util.KeyValueEntry; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; @Data public class TbRestApiCallNodeConfiguration implements NodeConfiguration { @@ -35,7 +38,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration queryParams; + private List<@NotNull KeyValueEntry> queryParams; private Map headers; private int readTimeoutMs; private int maxParallelRequestsCount; @@ -50,9 +53,23 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration queryParam : queryParams) { + if (queryParam == null || queryParam.key() == null || queryParam.value() == null) { + return false; + } + } + return true; + } + @Override public TbRestApiCallNodeConfiguration defaultConfiguration() { - TbRestApiCallNodeConfiguration configuration = new TbRestApiCallNodeConfiguration(); + var configuration = new TbRestApiCallNodeConfiguration(); configuration.setRestEndpointUrlPattern("http://localhost/api"); configuration.setRequestMethod("POST"); configuration.setHeaders(Collections.singletonMap(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)); @@ -68,11 +85,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration("email", "${userEmail}"), // ${} from metadata + new KeyValueEntry<>("device", "${deviceName}"), // ${} from metadata + new KeyValueEntry<>("${dynamicParam}", "${dynamicValue}"), // ${} in both key and value + new KeyValueEntry<>("temp", "$[temperature]"), // $[] from data + new KeyValueEntry<>("location", "$[sensor.location]") // $[] from nested data )); var metaData = new TbMsgMetaData(); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java index c51c044e18..90d4cdc47d 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java @@ -45,6 +45,7 @@ import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.data.util.KeyValueEntry; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -123,7 +124,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { public void shouldNotAllowNullQueryParamNames() { // GIVEN var config = new TbRestApiCallNodeConfiguration().defaultConfiguration(); - config.setQueryParams(List.of(new QueryParam(null, "value"))); + config.setQueryParams(List.of(new KeyValueEntry<>(null, "value"))); // WHEN-THEN assertThatThrownBy(() -> new TbRestApiCallNode().init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config)))) @@ -131,14 +132,14 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { .matches(e -> ((TbNodeException) e).isUnrecoverable()) .rootCause() .isInstanceOf(DataValidationException.class) - .hasMessageContaining("query parameter names must be non-null"); + .hasMessageContaining("query parameter names and values must be non-null"); } @Test public void shouldNotAllowNullQueryParamValues() { // GIVEN var config = new TbRestApiCallNodeConfiguration().defaultConfiguration(); - config.setQueryParams(List.of(new QueryParam("key", null))); + config.setQueryParams(List.of(new KeyValueEntry<>("key", null))); // WHEN-THEN assertThatThrownBy(() -> new TbRestApiCallNode().init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config)))) @@ -146,7 +147,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { .matches(e -> ((TbNodeException) e).isUnrecoverable()) .rootCause() .isInstanceOf(DataValidationException.class) - .hasMessageContaining("query parameter values must be non-null"); + .hasMessageContaining("query parameter names and values must be non-null"); } @Test @@ -154,8 +155,8 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { // GIVEN var config = new TbRestApiCallNodeConfiguration().defaultConfiguration(); - var queryParams = new ArrayList(); - queryParams.add(new QueryParam("key", "value")); + var queryParams = new ArrayList>(); + queryParams.add(new KeyValueEntry<>("key", "value")); queryParams.add(null); config.setQueryParams(queryParams);