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 3f7b45421a2..e6035acb102 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 47770cbae84..a1a3967d65c 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")) + } + }