This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git


The following commit(s) were added to refs/heads/main by this push:
     new 97f0fdb43f fix(operator): interpolate desc.url so failure message 
shows the URL (#6800)
97f0fdb43f is described below

commit 97f0fdb43fa7e14a501237a9f6662d7dd2ddbf56
Author: Kary Zheng <[email protected]>
AuthorDate: Fri Jul 24 14:48:00 2026 -0700

    fix(operator): interpolate desc.url so failure message shows the URL (#6800)
    
    ### What changes were proposed in this PR?
    
    Fixes a Scala string-interpolation bug in `URLFetcherOpExec`'s
    fetch-failure fallback.
    
    The failure branch built its message with `s"Fetch failed for URL:
    $desc.url"`. In an `s"..."` interpolator, `$desc` expands only the
    identifier `desc` (the `URLFetcherOpDesc` instance) and `.url` is
    appended as literal text. Because `LogicalOp` overrides `toString` with
    `ToStringBuilder.reflectionToString`, the resulting `URL content` cell
    contained the entire descriptor dump instead of the URL:
    
    ```
    Fetch failed for URL: 
org.apache.texera.amber.operator.source.fetcher.URLFetcherOpDesc@5624b9e8[decodingMethod=UTF_8,url=https://this-host-does-not-exist.invalid/x,dummyPropertyList=List(),inputPorts=<null>,operatorId=URLFetcherOpDesc-...,operatorVersion=N/A,outputPorts=<null>].url
    ```
    
    This both malforms the message and leaks internal operator fields
    (`operatorId`, `inputPorts`, `dummyPropertyList`, …) into user-facing
    output.
    
    The fix wraps the member access in braces so only `desc.url` is
    interpolated:
    
    ```diff
    -      case None => IOUtils.toInputStream(s"Fetch failed for URL: 
$desc.url", "UTF-8")
    +      case None => IOUtils.toInputStream(s"Fetch failed for URL: 
${desc.url}", "UTF-8")
    ```
    
    Now the message reads as intended:
    
    ```
    Fetch failed for URL: https://this-host-does-not-exist.invalid/x
    ```
    
    ### Any related issues, documentation, discussions?
    
    Closes #6755
    
    ### How was this PR tested?
    
    Added an automated regression test to `URLFetcherOpExecSpec` that
    exercises the fetch-failure branch **offline and deterministically**:
    pointing the operator at a `file://` URL for a nonexistent path makes
    `URLFetchUtil.getInputStreamFromURL` return `None` with no network
    dependency. The test asserts the fallback cell is exactly `Fetch failed
    for URL: <url>` and does **not** contain the descriptor dump
    (`URLFetcherOpDesc`, `operatorId`, …), so it fails on the pre-fix
    `$desc.url` behavior and passes with the fix.
    
    ```
    sbt "WorkflowOperator/testOnly 
org.apache.texera.amber.operator.source.fetcher.URLFetcherOpExecSpec"
    ...
    Tests: succeeded 3, failed 0, canceled 0, ignored 0, pending 0
    All tests passed.
    ```
    
    Also reproduced end-to-end before the fix by pointing a **URL Fetcher**
    operator at an unreachable address
    (`https://this-host-does-not-exist.invalid/x`) and inspecting the `URL
    content` output cell: it showed the full `URLFetcherOpDesc[...]` dump
    followed by `.url`; after the fix it shows `Fetch failed for URL:
    https://this-host-does-not-exist.invalid/x`.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Claude Opus 4.8)
    
    ---------
    
    Signed-off-by: Kary Zheng <[email protected]>
    Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
    Co-authored-by: Xinyuan Lin <[email protected]>
    Co-authored-by: Yicong Huang 
<[email protected]>
---
 .../operator/source/fetcher/URLFetcherOpExec.scala |  8 +++-----
 .../source/fetcher/URLFetcherOpExecSpec.scala      | 23 ++++++++++++++++++++++
 2 files changed, 26 insertions(+), 5 deletions(-)

diff --git 
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/source/fetcher/URLFetcherOpExec.scala
 
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/source/fetcher/URLFetcherOpExec.scala
index 3f7b45421a..e6035acb10 100644
--- 
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/source/fetcher/URLFetcherOpExec.scala
+++ 
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/source/fetcher/URLFetcherOpExec.scala
@@ -32,11 +32,9 @@ class URLFetcherOpExec(descString: String) extends 
SourceOperatorExecutor {
   override def produceTuple(): Iterator[TupleLike] = {
 
     val urlObj = new URL(desc.url)
-    val input = getInputStreamFromURL(urlObj)
-    val contentInputStream = input match {
-      case Some(value) => value
-      case None        => IOUtils.toInputStream(s"Fetch failed for URL: 
$desc.url", "UTF-8")
-    }
+    val contentInputStream = getInputStreamFromURL(urlObj).getOrElse(
+      IOUtils.toInputStream(s"Fetch failed for URL: ${desc.url}", "UTF-8")
+    )
     Iterator(if (desc.decodingMethod == DecodingMethod.UTF_8) {
       TupleLike(IOUtils.toString(contentInputStream, "UTF-8"))
     } else {
diff --git 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/source/fetcher/URLFetcherOpExecSpec.scala
 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/source/fetcher/URLFetcherOpExecSpec.scala
index 47770cbae8..a1a3967d65 100644
--- 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/source/fetcher/URLFetcherOpExecSpec.scala
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/source/fetcher/URLFetcherOpExecSpec.scala
@@ -44,4 +44,27 @@ class URLFetcherOpExecSpec extends AnyFlatSpec with 
BeforeAndAfter {
     assert(!iterator.hasNext)
   }
 
+  // On a failed fetch the fallback message must interpolate `desc.url` itself,
+  // not the descriptor's reflectionToString dump. A file:// URL to a 
nonexistent
+  // path makes getInputStreamFromURL return None deterministically and 
offline,
+  // so the failure branch is exercised without depending on external 
connectivity.
+  it should "report only the URL, not the operator descriptor, when the fetch 
fails" in {
+    val missingUrl =
+      java.nio.file.Files
+        .createTempDirectory("texera-urlfetcher-regression-")
+        .resolve("missing")
+        .toUri
+        .toString
+    opDesc.url = missingUrl
+    opDesc.decodingMethod = DecodingMethod.UTF_8
+    val fetcherOpExec = new 
URLFetcherOpExec(objectMapper.writeValueAsString(opDesc))
+    val content = 
fetcherOpExec.produceTuple().next().getFields.toList.head.asInstanceOf[String]
+
+    assert(content == s"Fetch failed for URL: ${opDesc.url}")
+    // Guard against the pre-fix `$desc.url` behavior, which leaked the whole
+    // descriptor dump (class name + internal fields) into the message.
+    assert(!content.contains("URLFetcherOpDesc"))
+    assert(!content.contains("operatorId"))
+  }
+
 }

Reply via email to