From a6ee4cfc374c23be84a2e18e882901d2e2b14f4c Mon Sep 17 00:00:00 2001 From: samyuktaprabhu <33195453+samyuktaprabhu@users.noreply.github.com> Date: Thu, 27 Aug 2026 09:49:49 +0200 Subject: [PATCH 1/3] chore: update data to payload --- .../com/sap/cds/feature/n8n/handlers/N8nHandler.java | 8 ++++---- .../cds/feature/n8n/handlers/N8nServiceHandler.java | 4 ++-- .../com/sap/cds/feature/n8n/services/N8nService.java | 10 +++++----- .../sap/cds/feature/n8n/services/N8nServiceImpl.java | 8 ++++---- .../com/sap/cds/feature/n8n/utils/InputExtractor.java | 6 +++--- .../feature/n8n/handlers/N8nServiceHandlerTest.java | 4 ++-- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/handlers/N8nHandler.java b/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/handlers/N8nHandler.java index beac40e..01727c2 100644 --- a/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/handlers/N8nHandler.java +++ b/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/handlers/N8nHandler.java @@ -299,13 +299,13 @@ public void afterAction(EventContext ctx) { HttpMethod.valueOf(annotatable.getAnnotationValue(ANNOTATION_START + ".method", "POST")); // Copy into a plain Map so InputExtractor can pull only the annotated fields from it - Map data = new HashMap<>(); - ctx.keySet().forEach(k -> data.put(k, ctx.get(k))); + Map ctxData = new HashMap<>(); + ctx.keySet().forEach(k -> ctxData.put(k, ctx.get(k))); Object ifExpr = annotatable.getAnnotationValue(ANNOTATION_START + ".if", null); - if (!ConditionEvaluator.evaluate(ifExpr, data)) return; + if (!ConditionEvaluator.evaluate(ifExpr, ctxData)) return; - Map payload = InputExtractor.extract(inputs, data); + Map payload = InputExtractor.extract(inputs, ctxData); if (props.isUseConsole()) { log.info("[console-n8n-service]: delivering n8n webhook path={} synchronously", path); diff --git a/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/handlers/N8nServiceHandler.java b/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/handlers/N8nServiceHandler.java index 890995c..0172332 100644 --- a/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/handlers/N8nServiceHandler.java +++ b/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/handlers/N8nServiceHandler.java @@ -50,14 +50,14 @@ public N8nServiceHandler( * Handles the {@code trigger} event. In console mode delivers synchronously via {@link * N8nWebhookService#notify}; otherwise submits an outbox message for deferred HTTP delivery. * - * @param ctx event context carrying {@code path} and {@code data} set by {@link + * @param ctx event context carrying {@code path} and {@code payload} set by {@link * com.sap.cds.feature.n8n.services.N8nServiceImpl#trigger} */ @On(event = "trigger") public void onTrigger(EventContext ctx) { String path = (String) ctx.get("path"); @SuppressWarnings("unchecked") - Map payload = (Map) ctx.get("data"); + Map payload = (Map) ctx.get("payload"); HttpMethod method = ctx.get("method") instanceof String m ? HttpMethod.valueOf(m) : HttpMethod.POST; if (props.isUseConsole()) { diff --git a/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/services/N8nService.java b/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/services/N8nService.java index 088d61b..fad1cac 100644 --- a/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/services/N8nService.java +++ b/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/services/N8nService.java @@ -23,10 +23,10 @@ public interface N8nService extends Service { * method to {@code POST} — the n8n webhook default. * * @param path webhook path appended to {@code n8n.base-url} - * @param data payload sent as JSON in the request body + * @param payload payload sent as JSON in the request body */ - default void trigger(String path, Map data) { - trigger(path, data, HttpMethod.POST); + default void trigger(String path, Map payload) { + trigger(path, payload, HttpMethod.POST); } /** @@ -36,8 +36,8 @@ default void trigger(String path, Map data) { * transaction commits, so a failing webhook never rolls back the business transaction. * * @param path webhook path appended to {@code n8n.base-url} - * @param data payload sent as JSON in the request body + * @param payload payload sent as JSON in the request body * @param method HTTP method to use (e.g. {@code HttpMethod.POST}, {@code HttpMethod.PUT}) */ - void trigger(String path, Map data, HttpMethod method); + void trigger(String path, Map payload, HttpMethod method); } diff --git a/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/services/N8nServiceImpl.java b/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/services/N8nServiceImpl.java index 13bb6c6..8c8990e 100644 --- a/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/services/N8nServiceImpl.java +++ b/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/services/N8nServiceImpl.java @@ -25,16 +25,16 @@ public N8nServiceImpl(String name) { /** * Emits a {@code trigger} event on the CAP event bus carrying {@code path}, {@code method}, and - * {@code data}. {@link com.sap.cds.feature.n8n.handlers.N8nServiceHandler} listens for this event - * and forwards it to the outbox. + * {@code payload}. {@link com.sap.cds.feature.n8n.handlers.N8nServiceHandler} listens for this + * event and forwards it to the outbox. */ @Override - public void trigger(String path, Map data, HttpMethod method) { + public void trigger(String path, Map payload, HttpMethod method) { // Create a named event so N8nServiceHandler can listen for it with @On(event = "trigger"); // null as the second argument means this event is not bound to any specific entity type EventContext ctx = EventContext.create("trigger", null); ctx.put("path", path); - ctx.put("data", data); + ctx.put("payload", payload); ctx.put("method", method.name()); // emit() dispatches through the CAP event bus, invoking registered @On handlers emit(ctx); diff --git a/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/utils/InputExtractor.java b/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/utils/InputExtractor.java index 44dcdc3..5916557 100644 --- a/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/utils/InputExtractor.java +++ b/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/utils/InputExtractor.java @@ -164,12 +164,12 @@ private static String leafKey(String path) { * @return the value at the path, or {@code null} if any segment is missing */ @SuppressWarnings("unchecked") - private static Object getNestedValue(String path, Map data) { + private static Object getNestedValue(String path, Map nestedValuesByKey) { int dot = path.indexOf('.'); if (dot < 0) { - return data.get(path); + return nestedValuesByKey.get(path); } - Object nested = data.get(path.substring(0, dot)); + Object nested = nestedValuesByKey.get(path.substring(0, dot)); return nested instanceof Map ? getNestedValue(path.substring(dot + 1), (Map) nested) : null; diff --git a/cds-feature-n8n/src/test/java/com/sap/cds/feature/n8n/handlers/N8nServiceHandlerTest.java b/cds-feature-n8n/src/test/java/com/sap/cds/feature/n8n/handlers/N8nServiceHandlerTest.java index a9f956f..5a8d3e7 100644 --- a/cds-feature-n8n/src/test/java/com/sap/cds/feature/n8n/handlers/N8nServiceHandlerTest.java +++ b/cds-feature-n8n/src/test/java/com/sap/cds/feature/n8n/handlers/N8nServiceHandlerTest.java @@ -58,7 +58,7 @@ void constructor_nullOutbox_consoleMode_doesNotThrow() { void onTrigger_submitsOutboxMessageAndSetsCompleted() { Map payload = Map.of("ID", "42", "title", "Dune"); when(ctx.get("path")).thenReturn("book-created"); - when(ctx.get("data")).thenReturn(payload); + when(ctx.get("payload")).thenReturn(payload); when(ctx.get("method")).thenReturn("POST"); handler.onTrigger(ctx); @@ -78,7 +78,7 @@ void onTrigger_consoleMode_callsWebhookDirectlyWithoutOutbox() { Map payload = Map.of("ID", "42", "title", "Dune"); when(props.isUseConsole()).thenReturn(true); when(ctx.get("path")).thenReturn("book-created"); - when(ctx.get("data")).thenReturn(payload); + when(ctx.get("payload")).thenReturn(payload); when(ctx.get("method")).thenReturn("POST"); handler.onTrigger(ctx); From 7ee9051d3025494163ea1dc6459b2cc527706d59 Mon Sep 17 00:00:00 2001 From: samyuktaprabhu <33195453+samyuktaprabhu@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:32:19 +0200 Subject: [PATCH 2/3] chore: update scalar to direct fields --- .../cds/feature/n8n/utils/InputExtractor.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/utils/InputExtractor.java b/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/utils/InputExtractor.java index 5916557..418ec5c 100644 --- a/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/utils/InputExtractor.java +++ b/cds-feature-n8n/src/main/java/com/sap/cds/feature/n8n/utils/InputExtractor.java @@ -31,18 +31,18 @@ private InputExtractor() {} /** * Extracts only the fields named in {@code inputs} from {@code row}. * - *

When {@code inputs} is empty, all scalar fields are returned — fields whose value is a + *

When {@code inputs} is empty, all direct fields are returned — fields whose value is a * {@link Map} (to-one association/composition) or a {@link Collection} (to-many) are excluded. * * @param inputs list of CDS path expressions ({@code String} or {@code {"=": "..."}}) or struct - * forms ({@code {path: ..., as: ...}}); empty means "all scalar fields" + * forms ({@code {path: ..., as: ...}}); empty means "all direct fields" * @param row the full entity row to extract from * @return a map containing the requested fields, keyed by the leaf segment or {@code as} alias */ public static Map extract(List inputs, Map row) { - // when inputs are empty, send all scalar fields + // when inputs are empty, send all direct fields if (inputs.isEmpty()) { - return getAllScalarFieldsByKey(row); + return getAllDirectFieldsByKey(row); } // else, when inputs are not empty Map fieldInputsByKey = new LinkedHashMap<>(); @@ -54,7 +54,7 @@ public static Map extract(List inputs, MapBare {@code $self} expands to all concrete non-association elements of {@code entity}. Plain - * scalar paths become {@link CQL#get} references; one-level association paths become {@link + * direct paths become {@link CQL#get} references; one-level association paths become {@link * com.sap.cds.ql.CQL#to(String) CQL.to(...).expand(...)} expands. Deep paths (more than one dot * after stripping the {@code $self.} prefix) are skipped. Returns an empty list when {@code * inputs} is empty, which the caller interprets as "no column restriction". @@ -73,7 +73,8 @@ public static List extractSelectables(List inputs, CdsStruct if (path == null) return Stream.empty(); int dot = path.indexOf('.'); if (dot < 0) { - // scalar already covered by bare $self expansion — skip to avoid duplicate column + // direct field already covered by bare $self expansion — skip to avoid duplicate + // column if (hasBareSelf) return Stream.empty(); return Stream.of(CQL.get(path)); } @@ -100,14 +101,14 @@ public static boolean isBareSelf(Object input) { return BARE_SELF.equals(resolvePath(input)); } - private static Map getAllScalarFieldsByKey(Map row) { - Map scalarFieldsByKey = new LinkedHashMap<>(); + private static Map getAllDirectFieldsByKey(Map row) { + Map directFieldsByKey = new LinkedHashMap<>(); row.forEach( (key, fieldValue) -> { if (!(fieldValue instanceof Map) && !(fieldValue instanceof Collection)) - scalarFieldsByKey.put(key, fieldValue); + directFieldsByKey.put(key, fieldValue); }); - return scalarFieldsByKey; + return directFieldsByKey; } private static void putInput( @@ -115,8 +116,8 @@ private static void putInput( String path = resolvePath(input); if (path != null) { if (BARE_SELF.equals(path)) { - // bare $self with no field — expand all scalar fields - fieldInputsByKey.putAll(getAllScalarFieldsByKey(row)); + // bare $self with no field — expand all direct fields + fieldInputsByKey.putAll(getAllDirectFieldsByKey(row)); return; } String field = stripSelfPrefix(path); From 210307e9e791421add8ff8282fc4bc208d6443d1 Mon Sep 17 00:00:00 2001 From: Lisa Julia Nebel Date: Thu, 27 Aug 2026 11:08:37 +0200 Subject: [PATCH 3/3] Found some more occurences of scalar --- README.md | 4 ++-- .../sap/cds/feature/n8n/handlers/N8nHandlerTest.java | 4 ++-- .../sap/cds/feature/n8n/utils/InputExtractorTest.java | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 347c0c3..8ddf3ee 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ When the annotated event fires, the plugin posts the selected `inputs` fields as } ``` -When `inputs` is omitted, all scalar fields of the entity are included in the payload. Specify `inputs` explicitly to limit which fields are sent — useful to avoid exposing sensitive or large fields. +When `inputs` is omitted, all direct fields of the entity are included in the payload. Specify `inputs` explicitly to limit which fields are sent — useful to avoid exposing sensitive or large fields. **Association fields** can be included using dot notation — the plugin issues a single expanded query to fetch the associated data: @@ -272,7 +272,7 @@ This produces a payload with the leaf field name as the key: > **Note:** > 1. Only one level of association traversal is supported (`$self.author.name`). Deeper paths (`$self.author.address.city`) are skipped with a warning. This is a known limitation compared to the Node.js plugin — contributions welcome. -> 2. Association paths are *not* resolved for `CREATE` events. For these, the plugin uses the raw request payload (the data as submitted), so association fields like `$self.author.name` will be `null`. Use scalar FK fields (e.g. `$self.author_ID`) for `CREATE` triggers instead. +> 2. Association paths are *not* resolved for `CREATE` events. For these, the plugin uses the raw request payload (the data as submitted), so association fields like `$self.author.name` will be `null`. Use direct FK fields (e.g. `$self.author_ID`) for `CREATE` triggers instead. ### Conditions diff --git a/cds-feature-n8n/src/test/java/com/sap/cds/feature/n8n/handlers/N8nHandlerTest.java b/cds-feature-n8n/src/test/java/com/sap/cds/feature/n8n/handlers/N8nHandlerTest.java index 856973f..85a44f6 100644 --- a/cds-feature-n8n/src/test/java/com/sap/cds/feature/n8n/handlers/N8nHandlerTest.java +++ b/cds-feature-n8n/src/test/java/com/sap/cds/feature/n8n/handlers/N8nHandlerTest.java @@ -874,7 +874,7 @@ private Map bareSelfPrefetchRow() { } @Test - void onDelete_assocPathBeforeBareSelf_payloadContainsAllScalarsAndAssocField() { + void onDelete_assocPathBeforeBareSelf_payloadContainsAllDirectsAndAssocField() { N8nHandler handlerForDelete = handlerWithFixedBareSelfRow(); when(deleteCtx.getTarget()).thenReturn(entity); @@ -952,7 +952,7 @@ protected Map fetchEntityRow( } @Test - void onDelete_bareSelfMixedWithAssocPath_payloadContainsAllScalarsAndAssocField() { + void onDelete_bareSelfMixedWithAssocPath_payloadContainsAllDirectsAndAssocField() { N8nHandler handlerForDelete = handlerWithFixedBareSelfRow(); when(deleteCtx.getTarget()).thenReturn(entity); diff --git a/cds-feature-n8n/src/test/java/com/sap/cds/feature/n8n/utils/InputExtractorTest.java b/cds-feature-n8n/src/test/java/com/sap/cds/feature/n8n/utils/InputExtractorTest.java index ec7589f..2c8b47d 100644 --- a/cds-feature-n8n/src/test/java/com/sap/cds/feature/n8n/utils/InputExtractorTest.java +++ b/cds-feature-n8n/src/test/java/com/sap/cds/feature/n8n/utils/InputExtractorTest.java @@ -40,7 +40,7 @@ void extract_selfPrefixIsStripped() { } @Test - void extract_bareSelf_expandsAllScalarFields() { + void extract_bareSelf_expandsAllDirectFields() { Map row = Map.of("ID", "1", "title", "Dune", "stock", 42); Map result = InputExtractor.extract(List.of(Map.of("=", "$self")), row); assertThat(result) @@ -50,7 +50,7 @@ void extract_bareSelf_expandsAllScalarFields() { } @Test - void extract_bareSelfMixedWithAssocPath_includesAllScalarsAndAssocField() { + void extract_bareSelfMixedWithAssocPath_includesAllDirectsAndAssocField() { Map row = new java.util.LinkedHashMap<>(); row.put("ID", "1"); row.put("title", "Dune"); @@ -125,7 +125,7 @@ void extract_missingField_returnsNull() { } @Test - void extract_emptyInputs_returnsAllScalarFields() { + void extract_emptyInputs_returnsAllDirectFields() { Map row = Map.of("ID", "1", "title", "Dune", "stock", 42); Map result = InputExtractor.extract(List.of(), row); assertThat(result) @@ -182,7 +182,7 @@ void isBareSelf_falseForFieldPath() { } @Test - void extractSelectables_bareSelf_emitsAllScalarColumns() { + void extractSelectables_bareSelf_emitsAllDirectColumns() { CdsEntity entity = mock(CdsEntity.class); CdsElement id = mock(CdsElement.class); CdsElement title = mock(CdsElement.class); @@ -197,7 +197,7 @@ void extractSelectables_bareSelf_emitsAllScalarColumns() { } @Test - void extractSelectables_bareSelfMixedWithScalarPath_noDuplicateColumns() { + void extractSelectables_bareSelfMixedWithDirectPath_noDuplicateColumns() { CdsEntity entity = mock(CdsEntity.class); CdsElement id = mock(CdsElement.class); CdsElement title = mock(CdsElement.class);