User-friendlier representation of a missing digest.

I moved it into DigestUtil preemptively in case we switch to binary instead of hex representation.

TESTED=manually
RELNOTES: None
PiperOrigin-RevId: 185007558
diff --git a/src/main/java/com/google/devtools/build/lib/remote/CacheNotFoundException.java b/src/main/java/com/google/devtools/build/lib/remote/CacheNotFoundException.java
index 15ff3ef..2f9e0c3 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/CacheNotFoundException.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/CacheNotFoundException.java
@@ -24,8 +24,8 @@
 public final class CacheNotFoundException extends IOException {
   private final Digest missingDigest;
 
-  CacheNotFoundException(Digest missingDigest) {
-    super("Missing digest: " + missingDigest);
+  CacheNotFoundException(Digest missingDigest, DigestUtil digestUtil) {
+    super("Missing digest: " + digestUtil.toString(missingDigest));
     this.missingDigest = missingDigest;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/remote/DigestUtil.java b/src/main/java/com/google/devtools/build/lib/remote/DigestUtil.java
index 2ef23b5..d3c93bb 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/DigestUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/DigestUtil.java
@@ -104,6 +104,10 @@
     return Digest.newBuilder().setHash(hexHash).setSizeBytes(size).build();
   }
 
+  public String toString(Digest digest) {
+    return digest.getHash() + "/" + digest.getSizeBytes();
+  }
+
   public static Digest getFromInputCache(ActionInput input, MetadataProvider cache)
       throws IOException {
     Metadata metadata = cache.getMetadata(input);
diff --git a/src/main/java/com/google/devtools/build/lib/remote/GrpcRemoteCache.java b/src/main/java/com/google/devtools/build/lib/remote/GrpcRemoteCache.java
index f0bc22b..377c50d 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/GrpcRemoteCache.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/GrpcRemoteCache.java
@@ -185,7 +185,7 @@
     if (!options.remoteInstanceName.isEmpty()) {
       resourceName += options.remoteInstanceName + "/";
     }
-    resourceName += "blobs/" + digest.getHash() + "/" + digest.getSizeBytes();
+    resourceName += "blobs/" + digestUtil.toString(digest);
     Iterator<ReadResponse> replies = bsBlockingStub()
         .read(ReadRequest.newBuilder().setResourceName(resourceName).build());
     while (replies.hasNext()) {
@@ -205,7 +205,7 @@
           });
     } catch (RetryException e) {
       if (RemoteRetrierUtils.causedByStatus(e, Status.Code.NOT_FOUND)) {
-        throw new CacheNotFoundException(digest);
+        throw new CacheNotFoundException(digest, digestUtil);
       }
       throw e;
     }
@@ -225,7 +225,7 @@
           });
     } catch (RetryException e) {
       if (RemoteRetrierUtils.causedByStatus(e, Status.Code.NOT_FOUND)) {
-        throw new CacheNotFoundException(digest);
+        throw new CacheNotFoundException(digest, digestUtil);
       }
       throw e;
     }
diff --git a/src/main/java/com/google/devtools/build/lib/remote/SimpleBlobStoreActionCache.java b/src/main/java/com/google/devtools/build/lib/remote/SimpleBlobStoreActionCache.java
index d9c67dc..21ad448 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/SimpleBlobStoreActionCache.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/SimpleBlobStoreActionCache.java
@@ -196,7 +196,7 @@
     ByteArrayOutputStream out = new ByteArrayOutputStream();
     boolean success = blobStore.getActionResult(digest.getHash(), out);
     if (!success) {
-      throw new CacheNotFoundException(digest);
+      throw new CacheNotFoundException(digest, digestUtil);
     }
     return out.toByteArray();
   }
@@ -216,7 +216,7 @@
     try (OutputStream out = dest.getOutputStream()) {
       boolean success = blobStore.get(digest.getHash(), out);
       if (!success) {
-        throw new CacheNotFoundException(digest);
+        throw new CacheNotFoundException(digest, digestUtil);
       }
     }
   }
@@ -229,7 +229,7 @@
     try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
       boolean success = blobStore.get(digest.getHash(), out);
       if (!success) {
-        throw new CacheNotFoundException(digest);
+        throw new CacheNotFoundException(digest, digestUtil);
       }
       return out.toByteArray();
     }
diff --git a/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java b/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java
index 7167f2f..25229f6 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java
@@ -342,7 +342,7 @@
         return new StreamObserver<WriteRequest>() {
           @Override
           public void onNext(WriteRequest request) {
-            assertThat(request.getResourceName()).contains(digest.getHash());
+            assertThat(request.getResourceName()).contains(DIGEST_UTIL.toString(digest));
             assertThat(request.getFinishWrite()).isTrue();
             assertThat(request.getData().toByteArray()).isEqualTo(data);
             responseObserver.onNext(
@@ -774,7 +774,8 @@
         new ByteStreamImplBase() {
           @Override
           public void read(ReadRequest request, StreamObserver<ReadResponse> responseObserver) {
-            assertThat(request.getResourceName().contains(stdOutDigest.getHash())).isTrue();
+            assertThat(request.getResourceName().contains(DIGEST_UTIL.toString(stdOutDigest)))
+                .isTrue();
             responseObserver.onError(Status.NOT_FOUND.asRuntimeException());
           }
         });
@@ -785,7 +786,7 @@
     } catch (SpawnExecException expected) {
       assertThat(expected.getSpawnResult().status())
           .isEqualTo(SpawnResult.Status.REMOTE_CACHE_FAILED);
-      assertThat(expected).hasMessageThat().contains(stdOutDigest.getHash());
+      assertThat(expected).hasMessageThat().contains(DIGEST_UTIL.toString(stdOutDigest));
       // Ensure we also got back the stack trace.
       assertThat(expected).hasMessageThat()
           .contains("GrpcRemoteExecutionClientTest.passCacheMissErrorWithStackTrace");
@@ -833,7 +834,8 @@
         new ByteStreamImplBase() {
           @Override
           public void read(ReadRequest request, StreamObserver<ReadResponse> responseObserver) {
-            assertThat(request.getResourceName().contains(stdOutDigest.getHash())).isTrue();
+            assertThat(request.getResourceName().contains(DIGEST_UTIL.toString(stdOutDigest)))
+                .isTrue();
             responseObserver.onError(Status.NOT_FOUND.asRuntimeException());
           }
         });
@@ -844,7 +846,7 @@
     } catch (SpawnExecException expected) {
       assertThat(expected.getSpawnResult().status())
           .isEqualTo(SpawnResult.Status.REMOTE_CACHE_FAILED);
-      assertThat(expected).hasMessageThat().contains(stdOutDigest.getHash());
+      assertThat(expected).hasMessageThat().contains(DIGEST_UTIL.toString(stdOutDigest));
       // Ensure we also got back the stack trace.
       assertThat(expected)
           .hasMessageThat()