diff --git a/README.md b/README.md index 1187b77..3d8c400 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ If the record collection is null, empty, or the field value is null, the respons - Works with **any SObject** - Field selection via **dynamic Field API Name** - Null-safe for records and field values +- Bulk-safe: each request is processed independently — one invalid field name or bad request does not fail others +- Per-request error reporting via the `errorMessage` output variable - No DML or SOQL - Uses `with sharing` to respect org security @@ -36,8 +38,10 @@ If the record collection is null, empty, or the field value is null, the respons ## Behavior Notes - The first record is determined by the **existing collection order** -- No sorting or validation of the field API name is performed -- If the field does not exist or the value is null, the response is `null` +- If `fieldApiName` is blank, `response` is `null` and `errorMessage` is populated +- If the field does not exist on the SObject, `response` is `null` and `errorMessage` contains the exception detail +- If the field value is `null`, `response` is `null` and `errorMessage` is also `null` - All returned values are converted to **String** +- When multiple requests are passed (bulk invocation), each is processed independently so that one failing request does not affect the others --- diff --git a/force-app/main/default/classes/CollectionGetFirstValue_Records.cls b/force-app/main/default/classes/CollectionGetFirstValue_Records.cls index 65c2537..d8c7c37 100644 --- a/force-app/main/default/classes/CollectionGetFirstValue_Records.cls +++ b/force-app/main/default/classes/CollectionGetFirstValue_Records.cls @@ -13,10 +13,22 @@ public with sharing class CollectionGetFirstValue_Records { continue; } - SObject firstRecord = req.records[0]; - Object fieldValue = firstRecord.get(req.fieldApiName); + if (String.isBlank(req.fieldApiName)) { + resp.response = null; + resp.errorMessage = 'The fieldApiName parameter is required and cannot be blank.'; + results.add(resp); + continue; + } + + try { + SObject firstRecord = req.records[0]; + Object fieldValue = firstRecord.get(req.fieldApiName); + resp.response = (fieldValue == null) ? null : String.valueOf(fieldValue); + } catch (SObjectException e) { + resp.response = null; + resp.errorMessage = e.getMessage(); + } - resp.response = (fieldValue == null) ? null : String.valueOf(fieldValue); results.add(resp); } @@ -34,5 +46,8 @@ public with sharing class CollectionGetFirstValue_Records { public class Response { @InvocableVariable(label='Response' description='Single text response containing the first field value') public String response; + + @InvocableVariable(label='Error Message' description='Contains error details if the field value could not be retrieved; null when successful.') + public String errorMessage; } } diff --git a/force-app/main/default/classes/CollectionGetFirstValue_RecordsTest.cls b/force-app/main/default/classes/CollectionGetFirstValue_RecordsTest.cls index 4008110..a09cc0b 100644 --- a/force-app/main/default/classes/CollectionGetFirstValue_RecordsTest.cls +++ b/force-app/main/default/classes/CollectionGetFirstValue_RecordsTest.cls @@ -49,4 +49,55 @@ public with sharing class CollectionGetFirstValue_RecordsTest { System.assertEquals(null, responses[0].response, 'Null records should result in null response'); } + @IsTest + static void testGetFirstValue_BlankFieldApiName() { + Account a = new Account(Name = 'Test Account'); + + CollectionGetFirstValue_Records.Request req = new CollectionGetFirstValue_Records.Request(); + req.records = new List{ a }; + req.fieldApiName = ''; + + List requests = new List{ req }; + + List responses = CollectionGetFirstValue_Records.getFirstValue(requests); + + System.assertEquals(1, responses.size()); + System.assertEquals(null, responses[0].response, 'Blank fieldApiName should result in null response'); + System.assertNotEquals(null, responses[0].errorMessage, 'Blank fieldApiName should populate errorMessage'); + } + + @IsTest + static void testGetFirstValue_InvalidFieldApiName_DoesNotBreakBatch() { + Account testAccount = new Account(Name = 'Good Account'); + + // Request 1: valid field + CollectionGetFirstValue_Records.Request reqGood = new CollectionGetFirstValue_Records.Request(); + reqGood.records = new List{ testAccount }; + reqGood.fieldApiName = 'Name'; + + // Request 2: invalid field — should not cause Request 1 or 3 to fail + CollectionGetFirstValue_Records.Request reqBad = new CollectionGetFirstValue_Records.Request(); + reqBad.records = new List{ testAccount }; + reqBad.fieldApiName = 'NonExistentField__xyz'; + + // Request 3: another valid field + CollectionGetFirstValue_Records.Request reqGood2 = new CollectionGetFirstValue_Records.Request(); + reqGood2.records = new List{ testAccount }; + reqGood2.fieldApiName = 'Name'; + + List requests = new List{ reqGood, reqBad, reqGood2 }; + + Test.startTest(); + List responses = CollectionGetFirstValue_Records.getFirstValue(requests); + Test.stopTest(); + + System.assertEquals(3, responses.size(), 'Should return a response for each request even when one has an invalid field'); + System.assertEquals('Good Account', responses[0].response, 'Valid request before the bad one should succeed'); + System.assertEquals(null, responses[0].errorMessage, 'Valid request should have no error'); + System.assertEquals(null, responses[1].response, 'Invalid field should result in null response'); + System.assertNotEquals(null, responses[1].errorMessage, 'Invalid field should populate errorMessage'); + System.assertEquals('Good Account', responses[2].response, 'Valid request after the bad one should succeed'); + System.assertEquals(null, responses[2].errorMessage, 'Valid request should have no error'); + } + } \ No newline at end of file