mirror of
https://github.com/esphome/esphome.git
synced 2025-07-28 14:16:40 +00:00
Fix compilation error when using string lambdas with homeassistant services (#9543)
This commit is contained in:
parent
cd987feb5b
commit
efcad565ee
@ -11,6 +11,15 @@ namespace esphome {
|
||||
namespace api {
|
||||
|
||||
template<typename... X> class TemplatableStringValue : public TemplatableValue<std::string, X...> {
|
||||
private:
|
||||
// Helper to convert value to string - handles the case where value is already a string
|
||||
template<typename T> static std::string value_to_string(T &&val) { return to_string(std::forward<T>(val)); }
|
||||
|
||||
// Overloads for string types - needed because std::to_string doesn't support them
|
||||
static std::string value_to_string(const char *val) { return std::string(val); } // For lambdas returning .c_str()
|
||||
static std::string value_to_string(const std::string &val) { return val; }
|
||||
static std::string value_to_string(std::string &&val) { return std::move(val); }
|
||||
|
||||
public:
|
||||
TemplatableStringValue() : TemplatableValue<std::string, X...>() {}
|
||||
|
||||
@ -19,7 +28,7 @@ template<typename... X> class TemplatableStringValue : public TemplatableValue<s
|
||||
|
||||
template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0>
|
||||
TemplatableStringValue(F f)
|
||||
: TemplatableValue<std::string, X...>([f](X... x) -> std::string { return to_string(f(x...)); }) {}
|
||||
: TemplatableValue<std::string, X...>([f](X... x) -> std::string { return value_to_string(f(x...)); }) {}
|
||||
};
|
||||
|
||||
template<typename... Ts> class TemplatableKeyValuePair {
|
||||
|
64
tests/integration/fixtures/api_string_lambda.yaml
Normal file
64
tests/integration/fixtures/api_string_lambda.yaml
Normal file
@ -0,0 +1,64 @@
|
||||
esphome:
|
||||
name: api-string-lambda-test
|
||||
host:
|
||||
|
||||
api:
|
||||
actions:
|
||||
# Service that tests string lambda functionality
|
||||
- action: test_string_lambda
|
||||
variables:
|
||||
input_string: string
|
||||
then:
|
||||
# Log the input to verify service was called
|
||||
- logger.log:
|
||||
format: "Service called with string: %s"
|
||||
args: [input_string.c_str()]
|
||||
|
||||
# This is the key test - using a lambda that returns x.c_str()
|
||||
# where x is already a string. This would fail to compile in 2025.7.0b5
|
||||
# with "no matching function for call to 'to_string(std::string)'"
|
||||
# This is the exact case from issue #9539
|
||||
- homeassistant.tag_scanned: !lambda 'return input_string.c_str();'
|
||||
|
||||
# Also test with homeassistant.event to verify our fix works with data fields
|
||||
- homeassistant.event:
|
||||
event: esphome.test_string_lambda
|
||||
data:
|
||||
value: !lambda 'return input_string.c_str();'
|
||||
|
||||
# Service that tests int lambda functionality
|
||||
- action: test_int_lambda
|
||||
variables:
|
||||
input_number: int
|
||||
then:
|
||||
# Log the input to verify service was called
|
||||
- logger.log:
|
||||
format: "Service called with int: %d"
|
||||
args: [input_number]
|
||||
|
||||
# Test that int lambdas still work correctly with to_string
|
||||
# The TemplatableStringValue should automatically convert int to string
|
||||
- homeassistant.event:
|
||||
event: esphome.test_int_lambda
|
||||
data:
|
||||
value: !lambda 'return input_number;'
|
||||
|
||||
# Service that tests float lambda functionality
|
||||
- action: test_float_lambda
|
||||
variables:
|
||||
input_float: float
|
||||
then:
|
||||
# Log the input to verify service was called
|
||||
- logger.log:
|
||||
format: "Service called with float: %.2f"
|
||||
args: [input_float]
|
||||
|
||||
# Test that float lambdas still work correctly with to_string
|
||||
# The TemplatableStringValue should automatically convert float to string
|
||||
- homeassistant.event:
|
||||
event: esphome.test_float_lambda
|
||||
data:
|
||||
value: !lambda 'return input_float;'
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
85
tests/integration/test_api_string_lambda.py
Normal file
85
tests/integration/test_api_string_lambda.py
Normal file
@ -0,0 +1,85 @@
|
||||
"""Integration test for TemplatableStringValue with string lambdas."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_string_lambda(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Test TemplatableStringValue works with lambdas that return different types."""
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
# Track log messages for all three service calls
|
||||
string_called_future = loop.create_future()
|
||||
int_called_future = loop.create_future()
|
||||
float_called_future = loop.create_future()
|
||||
|
||||
# Patterns to match in logs - confirms the lambdas compiled and executed
|
||||
string_pattern = re.compile(r"Service called with string: STRING_FROM_LAMBDA")
|
||||
int_pattern = re.compile(r"Service called with int: 42")
|
||||
float_pattern = re.compile(r"Service called with float: 3\.14")
|
||||
|
||||
def check_output(line: str) -> None:
|
||||
"""Check log output for expected messages."""
|
||||
if not string_called_future.done() and string_pattern.search(line):
|
||||
string_called_future.set_result(True)
|
||||
if not int_called_future.done() and int_pattern.search(line):
|
||||
int_called_future.set_result(True)
|
||||
if not float_called_future.done() and float_pattern.search(line):
|
||||
float_called_future.set_result(True)
|
||||
|
||||
# Run with log monitoring
|
||||
async with (
|
||||
run_compiled(yaml_config, line_callback=check_output),
|
||||
api_client_connected() as client,
|
||||
):
|
||||
# Verify device info
|
||||
device_info = await client.device_info()
|
||||
assert device_info is not None
|
||||
assert device_info.name == "api-string-lambda-test"
|
||||
|
||||
# List services to find our test services
|
||||
_, services = await client.list_entities_services()
|
||||
|
||||
# Find all test services
|
||||
string_service = next(
|
||||
(s for s in services if s.name == "test_string_lambda"), None
|
||||
)
|
||||
assert string_service is not None, "test_string_lambda service not found"
|
||||
|
||||
int_service = next((s for s in services if s.name == "test_int_lambda"), None)
|
||||
assert int_service is not None, "test_int_lambda service not found"
|
||||
|
||||
float_service = next(
|
||||
(s for s in services if s.name == "test_float_lambda"), None
|
||||
)
|
||||
assert float_service is not None, "test_float_lambda service not found"
|
||||
|
||||
# Execute all three services to test different lambda return types
|
||||
client.execute_service(string_service, {"input_string": "STRING_FROM_LAMBDA"})
|
||||
client.execute_service(int_service, {"input_number": 42})
|
||||
client.execute_service(float_service, {"input_float": 3.14})
|
||||
|
||||
# Wait for all service log messages
|
||||
# This confirms the lambdas compiled successfully and executed
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
asyncio.gather(
|
||||
string_called_future, int_called_future, float_called_future
|
||||
),
|
||||
timeout=5.0,
|
||||
)
|
||||
except TimeoutError:
|
||||
pytest.fail(
|
||||
"One or more service log messages not received - lambda may have failed to compile or execute"
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user