From 7b3c67723f04a97a8fa182192a1f12949827818a Mon Sep 17 00:00:00 2001 From: denis-troller Date: Mon, 29 Sep 2025 12:59:37 +0000 Subject: [PATCH 1/3] Create rule S8041 --- rules/S8041/apex/metadata.json | 25 +++++++++++++++++++ rules/S8041/apex/rule.adoc | 44 ++++++++++++++++++++++++++++++++++ rules/S8041/metadata.json | 2 ++ 3 files changed, 71 insertions(+) create mode 100644 rules/S8041/apex/metadata.json create mode 100644 rules/S8041/apex/rule.adoc create mode 100644 rules/S8041/metadata.json diff --git a/rules/S8041/apex/metadata.json b/rules/S8041/apex/metadata.json new file mode 100644 index 00000000000..f5822fcb931 --- /dev/null +++ b/rules/S8041/apex/metadata.json @@ -0,0 +1,25 @@ +{ + "title": "FIXME", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + ], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-8041", + "sqKey": "S8041", + "scope": "All", + "defaultQualityProfiles": ["Sonar way"], + "quickfix": "unknown", + "code": { + "impacts": { + "MAINTAINABILITY": "HIGH", + "RELIABILITY": "MEDIUM", + "SECURITY": "LOW" + }, + "attribute": "CONVENTIONAL" + } +} diff --git a/rules/S8041/apex/rule.adoc b/rules/S8041/apex/rule.adoc new file mode 100644 index 00000000000..3edb7d8d0d2 --- /dev/null +++ b/rules/S8041/apex/rule.adoc @@ -0,0 +1,44 @@ +FIXME: add a description + +// If you want to factorize the description uncomment the following line and create the file. +//include::../description.adoc[] + +== Why is this an issue? + +FIXME: remove the unused optional headers (that are commented out) + +//=== What is the potential impact? + +== How to fix it +//== How to fix it in FRAMEWORK NAME + +=== Code examples + +==== Noncompliant code example + +[source,apex,diff-id=1,diff-type=noncompliant] +---- +FIXME +---- + +==== Compliant solution + +[source,apex,diff-id=1,diff-type=compliant] +---- +FIXME +---- + +//=== How does this work? + +//=== Pitfalls + +//=== Going the extra mile + + +//== Resources +//=== Documentation +//=== Articles & blog posts +//=== Conference presentations +//=== Standards +//=== External coding guidelines +//=== Benchmarks diff --git a/rules/S8041/metadata.json b/rules/S8041/metadata.json new file mode 100644 index 00000000000..2c63c085104 --- /dev/null +++ b/rules/S8041/metadata.json @@ -0,0 +1,2 @@ +{ +} From 8726b7882eab0da075a55d91e65f9f88c388fe6c Mon Sep 17 00:00:00 2001 From: denis-troller Date: Mon, 29 Sep 2025 22:21:17 +0200 Subject: [PATCH 2/3] Update rules/S8041/apex/rule.adoc in PR #5647 --- rules/S8041/apex/rule.adoc | 91 ++++++++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 19 deletions(-) diff --git a/rules/S8041/apex/rule.adoc b/rules/S8041/apex/rule.adoc index 3edb7d8d0d2..360d65af77f 100644 --- a/rules/S8041/apex/rule.adoc +++ b/rules/S8041/apex/rule.adoc @@ -1,16 +1,27 @@ -FIXME: add a description - -// If you want to factorize the description uncomment the following line and create the file. -//include::../description.adoc[] +This rule raises an issue when an Apex HTTP callout is made without implementing retry logic to handle transient failures. == Why is this an issue? -FIXME: remove the unused optional headers (that are commented out) +Unlike Outbound Messaging, Apex callouts do not have built-in retry mechanisms. When you make an HTTP callout without retry logic, temporary network issues, service timeouts, or brief service unavailability will cause the callout to fail permanently. + +This creates several problems: + +* *Data loss*: Failed callouts may result in lost data that cannot be recovered +* *Poor user experience*: Users may see errors for temporary issues that could be resolved with a simple retry +* *Reduced system reliability*: Your integration becomes fragile and prone to failure +* *Increased support burden*: More manual intervention needed to handle failed integrations + +Transient failures are common in distributed systems. Network hiccups, temporary service overload, or brief maintenance windows can cause callouts to fail even when the target service is generally available. Without retry logic, these temporary issues become permanent failures. -//=== What is the potential impact? +Implementing retry logic with exponential backoff helps distinguish between temporary issues (which can be resolved by waiting and retrying) and permanent failures (which need different handling). This makes your integrations more robust and reliable. + +=== What is the potential impact? + +Failed callouts due to transient network issues can result in data loss, poor user experience, and reduced system reliability. Critical business processes that depend on external integrations may fail unnecessarily. == How to fix it -//== How to fix it in FRAMEWORK NAME + +Wrap HTTP callouts in retry logic with proper exception handling. Use a loop to retry failed requests, and implement exponential backoff to avoid overwhelming failing services. === Code examples @@ -18,27 +29,69 @@ FIXME: remove the unused optional headers (that are commented out) [source,apex,diff-id=1,diff-type=noncompliant] ---- -FIXME +@future(callout=true) +public static void makeCallout() { + Http http = new Http(); + HttpRequest request = new HttpRequest(); + request.setEndpoint('https://api.example.com/data'); + request.setMethod('POST'); + HttpResponse response = http.send(request); // Noncompliant + // No retry logic - fails permanently on transient issues +} ---- ==== Compliant solution [source,apex,diff-id=1,diff-type=compliant] ---- -FIXME +@future(callout=true) +public static void makeCallout() { + Integer maxRetries = 3; + Integer retryDelay = 1000; // Start with 1 second + + for (Integer attempt = 0; attempt < maxRetries; attempt++) { + try { + Http http = new Http(); + HttpRequest request = new HttpRequest(); + request.setEndpoint('https://api.example.com/data'); + request.setMethod('POST'); + request.setTimeout(10000); + + HttpResponse response = http.send(request); + + // Success - exit retry loop + if (response.getStatusCode() >= 200 && response.getStatusCode() < 300) { + break; + } + + // Server error - retry + if (response.getStatusCode() >= 500 && attempt < maxRetries - 1) { + System.debug('Server error, retrying in ' + retryDelay + 'ms'); + // Exponential backoff + retryDelay *= 2; + continue; + } + + } catch (Exception e) { + if (attempt == maxRetries - 1) { + // Final attempt failed - rethrow + throw e; + } + System.debug('Callout failed, retrying: ' + e.getMessage()); + retryDelay *= 2; + } + } +} ---- -//=== How does this work? +== Resources + +=== Documentation -//=== Pitfalls + * Apex HTTP Callouts - https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_callouts_http.htm[Official Salesforce documentation on making HTTP callouts in Apex] -//=== Going the extra mile + * Callout Limits and Timeouts - https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_callouts_timeouts.htm[Documentation on Apex callout limits and timeout handling] +=== Standards -//== Resources -//=== Documentation -//=== Articles & blog posts -//=== Conference presentations -//=== Standards -//=== External coding guidelines -//=== Benchmarks + * CWE-754: Improper Check for Unusual or Exceptional Conditions - https://cwe.mitre.org/data/definitions/754.html[Failure to handle exceptional conditions like network timeouts] From 009165878a8a28fde359bf050185461977697c58 Mon Sep 17 00:00:00 2001 From: denis-troller Date: Mon, 29 Sep 2025 22:21:20 +0200 Subject: [PATCH 3/3] Update rules/S8041/apex/metadata.json in PR #5647 --- rules/S8041/apex/metadata.json | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/rules/S8041/apex/metadata.json b/rules/S8041/apex/metadata.json index f5822fcb931..0a07b47afa7 100644 --- a/rules/S8041/apex/metadata.json +++ b/rules/S8041/apex/metadata.json @@ -1,25 +1,29 @@ { - "title": "FIXME", + "title": "Apex callouts should implement retry logic for reliability", "type": "CODE_SMELL", "status": "ready", "remediation": { - "func": "Constant\/Issue", - "constantCost": "5min" + "func": "Constant/Issue", + "constantCost": "30 min" }, "tags": [ + "reliability", + "network", + "integration" ], - "defaultSeverity": "Major", + "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-8041", "sqKey": "S8041", - "scope": "All", - "defaultQualityProfiles": ["Sonar way"], + "scope": "Main", + "defaultQualityProfiles": [ + "Sonar way" + ], "quickfix": "unknown", "code": { "impacts": { - "MAINTAINABILITY": "HIGH", - "RELIABILITY": "MEDIUM", - "SECURITY": "LOW" + "RELIABILITY": "BLOCKER", + "MAINTAINABILITY": "BLOCKER" }, - "attribute": "CONVENTIONAL" + "attribute": "COMPLETE" } -} +} \ No newline at end of file