openems icon indicating copy to clipboard operation
openems copied to clipboard

Move Regex Patterns from URI Path to Query Parameters and Request Body

Open Sn0w3y opened this issue 9 months ago • 6 comments

Problem

The current REST API for OpenEMS allows regex patterns in the URI path (e.g., /rest/channel/Battery[1-3]/Status[0-9]). This can lead to URI compliance violations as square brackets and other special characters used in regex patterns might not be properly handled in all HTTP clients and servers.

Solution

This PR modifies the REST API to support regex patterns in query parameters and request body, while maintaining backward compatibility with the existing approach.

Key Changes

  1. Added support for query parameters to specify regex patterns
  2. Added support for regex patterns in request body for POST requests
  3. Maintained backward compatibility with the existing path-based approach
  4. Improved error handling and debugging for regex patterns
  5. Implemented proper parsing of query parameters from the request URI
  6. Added comprehensive test coverage for the new functionality

Usage Examples

Original Approach (Still Supported)

GET Request with regex in path:

GET /rest/channel/Battery[1-3]/State.*

POST Request with regex in path:

POST /rest/channel/Battery[1-3]/State.*
{
  "value": 42
}

New Approach 1: Query Parameters

GET Request with regex in query parameters:

GET /rest/channel?componentRegex=Battery[1-3]&channelRegex=State.*

GET Request with exact component and regex channel:

GET /rest/channel?component=Battery1&channelRegex=State.*

New Approach 2: Request Body (POST only)

POST Request with regex in body:

POST /rest/channel
{
  "componentRegex": "Battery[1-3]",
  "channelRegex": "State.*",
  "value": 42
}

POST Request with mixed approach:

POST /rest/channel/Battery[1-3]?channelRegex=State.*
{
  "value": 42
}

Benefits

  1. Better compatibility with URI standards and HTTP clients
  2. More flexible matching capabilities, especially for complex patterns
  3. Separation of concerns: path for resource identification, query parameters for filtering, body for data
  4. Improved error messages when regex patterns are invalid

Implementation Notes

  • The implementation preserves backward compatibility with both API users and existing test cases
  • Query parameters take precedence over path components if both are specified
  • Request body regex parameters take precedence over query parameters if both are specified
  • A new utility method was added to parse query parameters from the URI
  • Comprehensive unit tests have been added to verify both the legacy functionality and the new approaches

This change addresses the need to move regex patterns out of the URI path to improve compliance with URI standards, making the API more robust across different clients and servers.

Sn0w3y avatar Mar 28 '25 10:03 Sn0w3y

@michaelgrill @sfeilmeier maybe have a look :)

Sn0w3y avatar Mar 28 '25 10:03 Sn0w3y

Codecov Report

Attention: Patch coverage is 35.29412% with 44 lines in your changes missing coverage. Please review.

:x: Your patch check has failed because the patch coverage (35.30%) is below the target coverage (75.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@              Coverage Diff              @@
##             develop    #3074      +/-   ##
=============================================
- Coverage      58.85%   58.81%   -0.03%     
  Complexity       159      159              
=============================================
  Files           2506     2506              
  Lines         107183   107237      +54     
  Branches        7926     7949      +23     
=============================================
- Hits           63074    63066       -8     
- Misses         41787    41837      +50     
- Partials        2322     2334      +12     
:rocket: New features to boost your workflow:
  • :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • :package: JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

codecov[bot] avatar Mar 28 '25 10:03 codecov[bot]

@Sn0w3y , We were thinking to simplify and also restructure the channle access API. We want to get rid of regex patterns, and is much difficult for customer.

What we are thinking was -

  1. GET method - Single Channel Only

Remove regex support from GET requests URL: GET /rest/channel/{componentId}/{channelId} Returns value for exactly one specific channel Simple, straightforward for customers

  1. POST Method - Multiple Channels

URL: POST /rest/channel JSON body specifies which channels are needed One REST call can retrieve multiple channel values More efficient for bulk operations

pooran-c avatar Jul 21 '25 09:07 pooran-c

@Sn0w3y , We were thinking to simplify and also restructure the channle access API. We want to get rid of regex patterns, and is much difficult for customer.

What we are thinking was -

  1. GET method - Single Channel Only

Remove regex support from GET requests URL: GET /rest/channel/{componentId}/{channelId} Returns value for exactly one specific channel Simple, straightforward for customers

  1. POST Method - Multiple Channels

URL: POST /rest/channel JSON body specifies which channels are needed One REST call can retrieve multiple channel values More efficient for bulk operations

Your proposal to simplify is excellent for most use cases. However, consider these scenarios where pattern matching is valuable:

  1. Monitoring multiple ESS units: Battery[1-3]/.* gets all channels from 3 batteries in one call
  2. Getting all power-related channels: .Power. across all components
  3. System integrators who need flexible data collection without knowing exact channel names

What if we implement both approaches?

// Your proposal - Simple single channel GET /rest/channel/ess0/ActivePower

// Bulk operation without regex (new endpoint)

POST /rest/channels
{
  "channels": [
    {"component": "ess0", "channel": "ActivePower"},
    {"component": "ess1", "channel": "ActivePower"},
    {"component": "_sum", "channel": "GridActivePower"}
  ]
}

// Keep regex support for advanced users (with query params for URI compliance) GET /rest/channel?componentRegex=Battery[1-3]&channelRegex=.*Power This way:

  • Simple use cases remain simple
  • Advanced users retain pattern matching capabilities
  • URI compliance is improved
  • We don't break existing integrations

Would this combined approach work for your use case? We could even add a configuration flag to disable regex support entirely for installations that don't need it.

What do you think?

Sn0w3y avatar Jul 29 '25 22:07 Sn0w3y

It makes sense to introduce query parameters while keeping backward compatibility. However, I see a few concerns:

  1. Mixed usage issues
  • Example: /rest/channel/ess0?channelRegex=Soc fails because the method still expects two path segments, even when channelRegex is provided. The logic throws a "Missing channel ID" error before checking if regex patterns are available.

  • Example: /rest/channel/ess0/Soc?componentRegex=ess[0-8] works, but the intent is unclear.

  1. POST should handle single operations only
  • Currently, a POST with componentRegex supports bulk updates across multiple components. For instance:

/rest/channel/ess0/SetActivePowerEquals?componentRegex=ess[0-8] applies changes to ess0–ess8.

This behavior creates unclear intent. POST operations should be limited to single component/channel pairs to avoid unintended modifications.

pooran-c avatar Sep 04 '25 13:09 pooran-c

It makes sense to introduce query parameters while keeping backward compatibility. However, I see a few concerns:

  1. Mixed usage issues
  • Example: /rest/channel/ess0?channelRegex=Soc fails because the method still expects two path segments, even when channelRegex is provided. The logic throws a "Missing channel ID" error before checking if regex patterns are available.
  • Example: /rest/channel/ess0/Soc?componentRegex=ess[0-8] works, but the intent is unclear.
  1. POST should handle single operations only
  • Currently, a POST with componentRegex supports bulk updates across multiple components. For instance:

/rest/channel/ess0/SetActivePowerEquals?componentRegex=ess[0-8] applies changes to ess0–ess8.

This behavior creates unclear intent. POST operations should be limited to single component/channel pairs to avoid unintended modifications.

1) Mixed Usage / Path Segments

handleChannel will accept 0..2 path segments (not strictly targets.size() == 2).

Precedence: Body regex > Query regex > Path exact

  • If any regex is present in the body or query, we switch to regex matching mode and do not throw "Missing channel ID" simply because the path omitted a second segment.
  • If no regex present, we require exact component + channel (path or component/channel query params).

This fixes /rest/channel/ess0?channelRegex=Soc (treated as regex request for component ess0) and removes the "Missing channel ID" before regex checks.

2) POST and Bulk Safety

POST default (single): POST /rest/channel/{component}/{channel} with { "value": ... } remains a single-channel update.

Bulk (explicit only): Bulk updates via regex are only allowed when the request body contains componentRegex/channelRegex and confirmBulk: true.

Examples:

Allowed bulk:

POST /rest/channel
{
  "componentRegex": "ess[0-8]",
  "channelRegex": "SetActivePower.*",
  "value": 42,
  "confirmBulk": true
}

Disallowed bulk:

POST /rest/channel/ess0/SetActivePower?componentRegex=ess[0-8]

Ambiguous and therefore rejected (400) unless the body explicitly sets confirmBulk: true.

Alternatively we can add a dedicated bulk endpoint (POST /rest/channels) later, but for now confirmBulk ensures safety without breaking existing behaviour.

3) Mixed Path+Query Ambiguity

If path contains exact IDs and query/body contains regex:

  • For GET: regex mode applies (componentRegex/channelRegex can broaden/override the exact path per precedence rules)
  • For POST: if any regex is present, require confirmBulk: true in body to proceed; otherwise treat as single update for the path pair

4) Tests & Docs

I'll add unit tests for:

  • /rest/channel/ess0?channelRegex=Soc (GET → OK)
  • /rest/channel/ess0/Soc?componentRegex=ess[0-8] (clarified behaviour)
  • POST single (legacy)
  • POST bulk via body with/without confirmBulk (accept/reject)

Will also document precedence and the confirmBulk requirement in the API docs/PR description.

If that sounds good I'll update the PR accordingly (small code changes in handleChannel, handleGet/handlePost overloads, and tests).

Thanks again — this makes the API both safer and clearer.

Sn0w3y avatar Sep 04 '25 14:09 Sn0w3y