• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #19403

05 Aug 2024 08:31PM UTC coverage: 84.458% (-0.002%) from 84.46%
#19403

push

github

ejona86
Migrate from the deprecated `Charsets` constants (in Guava) to the `StandardCharsets` constants (in the JDK)

cl/658539667

33267 of 39389 relevant lines covered (84.46%)

0.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.99
/../api/src/main/java/io/grpc/Status.java
1
/*
2
 * Copyright 2014 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20
import static com.google.common.base.Throwables.getStackTraceAsString;
21
import static java.nio.charset.StandardCharsets.US_ASCII;
22
import static java.nio.charset.StandardCharsets.UTF_8;
23

24
import com.google.common.base.MoreObjects;
25
import com.google.common.base.Objects;
26
import io.grpc.Metadata.TrustedAsciiMarshaller;
27
import java.nio.ByteBuffer;
28
import java.util.ArrayList;
29
import java.util.Arrays;
30
import java.util.Collections;
31
import java.util.List;
32
import java.util.TreeMap;
33
import javax.annotation.CheckReturnValue;
34
import javax.annotation.Nullable;
35
import javax.annotation.concurrent.Immutable;
36

37
/**
38
 * Defines the status of an operation by providing a standard {@link Code} in conjunction with an
39
 * optional descriptive message. Instances of {@code Status} are created by starting with the
40
 * template for the appropriate {@link Status.Code} and supplementing it with additional
41
 * information: {@code Status.NOT_FOUND.withDescription("Could not find 'important_file.txt'");}
42
 *
43
 * <p>For clients, every remote call will return a status on completion. In the case of errors this
44
 * status may be propagated to blocking stubs as a {@link RuntimeException} or to a listener as an
45
 * explicit parameter.
46
 *
47
 * <p>Similarly servers can report a status by throwing {@link StatusRuntimeException}
48
 * or by passing the status to a callback.
49
 *
50
 * <p>Utility functions are provided to convert a status to an exception and to extract them
51
 * back out.
52
 *
53
 * <p>Extended descriptions, including a list of codes that should not be generated by the library,
54
 * can be found at
55
 * <a href="https://github.com/grpc/grpc/blob/master/doc/statuscodes.md">doc/statuscodes.md</a>
56
 */
57
@Immutable
58
@CheckReturnValue
59
public final class Status {
60

61
  /**
62
   * The set of canonical status codes. If new codes are added over time they must choose
63
   * a numerical value that does not collide with any previously used value.
64
   */
65
  public enum Code {
1✔
66
    /**
67
     * The operation completed successfully.
68
     */
69
    OK(0),
1✔
70

71
    /**
72
     * The operation was cancelled (typically by the caller).
73
     */
74
    CANCELLED(1),
1✔
75

76
    /**
77
     * Unknown error.  An example of where this error may be returned is
78
     * if a Status value received from another address space belongs to
79
     * an error-space that is not known in this address space.  Also
80
     * errors raised by APIs that do not return enough error information
81
     * may be converted to this error.
82
     */
83
    UNKNOWN(2),
1✔
84

85
    /**
86
     * Client specified an invalid argument.  Note that this differs
87
     * from FAILED_PRECONDITION.  INVALID_ARGUMENT indicates arguments
88
     * that are problematic regardless of the state of the system
89
     * (e.g., a malformed file name).
90
     */
91
    INVALID_ARGUMENT(3),
1✔
92

93
    /**
94
     * Deadline expired before operation could complete.  For operations
95
     * that change the state of the system, this error may be returned
96
     * even if the operation has completed successfully.  For example, a
97
     * successful response from a server could have been delayed long
98
     * enough for the deadline to expire.
99
     */
100
    DEADLINE_EXCEEDED(4),
1✔
101

102
    /**
103
     * Some requested entity (e.g., file or directory) was not found.
104
     */
105
    NOT_FOUND(5),
1✔
106

107
    /**
108
     * Some entity that we attempted to create (e.g., file or directory) already exists.
109
     */
110
    ALREADY_EXISTS(6),
1✔
111

112
    /**
113
     * The caller does not have permission to execute the specified
114
     * operation.  PERMISSION_DENIED must not be used for rejections
115
     * caused by exhausting some resource (use RESOURCE_EXHAUSTED
116
     * instead for those errors).  PERMISSION_DENIED must not be
117
     * used if the caller cannot be identified (use UNAUTHENTICATED
118
     * instead for those errors).
119
     */
120
    PERMISSION_DENIED(7),
1✔
121

122
    /**
123
     * Some resource has been exhausted, perhaps a per-user quota, or
124
     * perhaps the entire file system is out of space.
125
     */
126
    RESOURCE_EXHAUSTED(8),
1✔
127

128
    /**
129
     * Operation was rejected because the system is not in a state
130
     * required for the operation's execution.  For example, directory
131
     * to be deleted may be non-empty, an rmdir operation is applied to
132
     * a non-directory, etc.
133
     *
134
     * <p>A litmus test that may help a service implementor in deciding
135
     * between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:
136
     * (a) Use UNAVAILABLE if the client can retry just the failing call.
137
     * (b) Use ABORTED if the client should retry at a higher-level
138
     * (e.g., restarting a read-modify-write sequence).
139
     * (c) Use FAILED_PRECONDITION if the client should not retry until
140
     * the system state has been explicitly fixed.  E.g., if an "rmdir"
141
     * fails because the directory is non-empty, FAILED_PRECONDITION
142
     * should be returned since the client should not retry unless
143
     * they have first fixed up the directory by deleting files from it.
144
     */
145
    FAILED_PRECONDITION(9),
1✔
146

147
    /**
148
     * The operation was aborted, typically due to a concurrency issue
149
     * like sequencer check failures, transaction aborts, etc.
150
     *
151
     * <p>See litmus test above for deciding between FAILED_PRECONDITION,
152
     * ABORTED, and UNAVAILABLE.
153
     */
154
    ABORTED(10),
1✔
155

156
    /**
157
     * Operation was attempted past the valid range.  E.g., seeking or
158
     * reading past end of file.
159
     *
160
     * <p>Unlike INVALID_ARGUMENT, this error indicates a problem that may
161
     * be fixed if the system state changes. For example, a 32-bit file
162
     * system will generate INVALID_ARGUMENT if asked to read at an
163
     * offset that is not in the range [0,2^32-1], but it will generate
164
     * OUT_OF_RANGE if asked to read from an offset past the current
165
     * file size.
166
     *
167
     * <p>There is a fair bit of overlap between FAILED_PRECONDITION and OUT_OF_RANGE.
168
     * We recommend using OUT_OF_RANGE (the more specific error) when it applies
169
     * so that callers who are iterating through
170
     * a space can easily look for an OUT_OF_RANGE error to detect when they are done.
171
     */
172
    OUT_OF_RANGE(11),
1✔
173

174
    /**
175
     * Operation is not implemented or not supported/enabled in this service.
176
     */
177
    UNIMPLEMENTED(12),
1✔
178

179
    /**
180
     * Internal errors.  Means some invariants expected by underlying
181
     * system has been broken.  If you see one of these errors,
182
     * something is very broken.
183
     */
184
    INTERNAL(13),
1✔
185

186
    /**
187
     * The service is currently unavailable.  This is a most likely a
188
     * transient condition and may be corrected by retrying with
189
     * a backoff. Note that it is not always safe to retry
190
     * non-idempotent operations.
191
     *
192
     * <p>See litmus test above for deciding between FAILED_PRECONDITION,
193
     * ABORTED, and UNAVAILABLE.
194
     */
195
    UNAVAILABLE(14),
1✔
196

197
    /**
198
     * Unrecoverable data loss or corruption.
199
     */
200
    DATA_LOSS(15),
1✔
201

202
    /**
203
     * The request does not have valid authentication credentials for the
204
     * operation.
205
     */
206
    UNAUTHENTICATED(16);
1✔
207

208
    private final int value;
209
    @SuppressWarnings("ImmutableEnumChecker") // we make sure the byte[] can't be modified
210
    private final byte[] valueAscii;
211

212
    private Code(int value) {
1✔
213
      this.value = value;
1✔
214
      this.valueAscii = Integer.toString(value).getBytes(US_ASCII);
1✔
215
    }
1✔
216

217
    /**
218
     * The numerical value of the code.
219
     */
220
    public int value() {
221
      return value;
1✔
222
    }
223

224
    /**
225
     * Returns a {@link Status} object corresponding to this status code.
226
     */
227
    public Status toStatus() {
228
      return STATUS_LIST.get(value);
1✔
229
    }
230

231
    private byte[] valueAscii() {
232
      return valueAscii;
1✔
233
    }
234
  }
235

236
  // Create the canonical list of Status instances indexed by their code values.
237
  private static final List<Status> STATUS_LIST = buildStatusList();
1✔
238

239
  private static List<Status> buildStatusList() {
240
    TreeMap<Integer, Status> canonicalizer = new TreeMap<>();
1✔
241
    for (Code code : Code.values()) {
1✔
242
      Status replaced = canonicalizer.put(code.value(), new Status(code));
1✔
243
      if (replaced != null) {
1✔
244
        throw new IllegalStateException("Code value duplication between "
×
245
            + replaced.getCode().name() + " & " + code.name());
×
246
      }
247
    }
248
    return Collections.unmodifiableList(new ArrayList<>(canonicalizer.values()));
1✔
249
  }
250

251
  // A pseudo-enum of Status instances mapped 1:1 with values in Code. This simplifies construction
252
  // patterns for derived instances of Status.
253
  /** The operation completed successfully. */
254
  public static final Status OK = Code.OK.toStatus();
1✔
255
  /** The operation was cancelled (typically by the caller). */
256
  public static final Status CANCELLED = Code.CANCELLED.toStatus();
1✔
257
  /** Unknown error. See {@link Code#UNKNOWN}. */
258
  public static final Status UNKNOWN = Code.UNKNOWN.toStatus();
1✔
259
  /** Client specified an invalid argument. See {@link Code#INVALID_ARGUMENT}. */
260
  public static final Status INVALID_ARGUMENT = Code.INVALID_ARGUMENT.toStatus();
1✔
261
  /** Deadline expired before operation could complete. See {@link Code#DEADLINE_EXCEEDED}. */
262
  public static final Status DEADLINE_EXCEEDED = Code.DEADLINE_EXCEEDED.toStatus();
1✔
263
  /** Some requested entity (e.g., file or directory) was not found. */
264
  public static final Status NOT_FOUND = Code.NOT_FOUND.toStatus();
1✔
265
  /** Some entity that we attempted to create (e.g., file or directory) already exists. */
266
  public static final Status ALREADY_EXISTS = Code.ALREADY_EXISTS.toStatus();
1✔
267
  /**
268
   * The caller does not have permission to execute the specified operation. See {@link
269
   * Code#PERMISSION_DENIED}.
270
   */
271
  public static final Status PERMISSION_DENIED = Code.PERMISSION_DENIED.toStatus();
1✔
272
  /** The request does not have valid authentication credentials for the operation. */
273
  public static final Status UNAUTHENTICATED = Code.UNAUTHENTICATED.toStatus();
1✔
274
  /**
275
   * Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system
276
   * is out of space.
277
   */
278
  public static final Status RESOURCE_EXHAUSTED = Code.RESOURCE_EXHAUSTED.toStatus();
1✔
279
  /**
280
   * Operation was rejected because the system is not in a state required for the operation's
281
   * execution. See {@link Code#FAILED_PRECONDITION}.
282
   */
283
  public static final Status FAILED_PRECONDITION =
1✔
284
      Code.FAILED_PRECONDITION.toStatus();
1✔
285
  /**
286
   * The operation was aborted, typically due to a concurrency issue like sequencer check failures,
287
   * transaction aborts, etc. See {@link Code#ABORTED}.
288
   */
289
  public static final Status ABORTED = Code.ABORTED.toStatus();
1✔
290
  /** Operation was attempted past the valid range. See {@link Code#OUT_OF_RANGE}. */
291
  public static final Status OUT_OF_RANGE = Code.OUT_OF_RANGE.toStatus();
1✔
292
  /** Operation is not implemented or not supported/enabled in this service. */
293
  public static final Status UNIMPLEMENTED = Code.UNIMPLEMENTED.toStatus();
1✔
294
  /** Internal errors. See {@link Code#INTERNAL}. */
295
  public static final Status INTERNAL = Code.INTERNAL.toStatus();
1✔
296
  /** The service is currently unavailable. See {@link Code#UNAVAILABLE}. */
297
  public static final Status UNAVAILABLE = Code.UNAVAILABLE.toStatus();
1✔
298
  /** Unrecoverable data loss or corruption. */
299
  public static final Status DATA_LOSS = Code.DATA_LOSS.toStatus();
1✔
300

301
  /**
302
   * Return a {@link Status} given a canonical error {@link Code} value.
303
   */
304
  public static Status fromCodeValue(int codeValue) {
305
    if (codeValue < 0 || codeValue >= STATUS_LIST.size()) {
1✔
306
      return UNKNOWN.withDescription("Unknown code " + codeValue);
1✔
307
    } else {
308
      return STATUS_LIST.get(codeValue);
1✔
309
    }
310
  }
311

312
  private static Status fromCodeValue(byte[] asciiCodeValue) {
313
    if (asciiCodeValue.length == 1 && asciiCodeValue[0] == '0') {
1✔
314
      return Status.OK;
1✔
315
    }
316
    return fromCodeValueSlow(asciiCodeValue);
1✔
317
  }
318

319
  @SuppressWarnings("fallthrough")
320
  private static Status fromCodeValueSlow(byte[] asciiCodeValue) {
321
    int index = 0;
1✔
322
    int codeValue = 0;
1✔
323
    switch (asciiCodeValue.length) {
1✔
324
      case 2:
325
        if (asciiCodeValue[index] < '0' || asciiCodeValue[index] > '9') {
1✔
326
          break;
×
327
        }
328
        codeValue += (asciiCodeValue[index++] - '0') * 10;
1✔
329
        // fall through
330
      case 1:
331
        if (asciiCodeValue[index] < '0' || asciiCodeValue[index] > '9') {
1✔
332
          break;
×
333
        }
334
        codeValue += asciiCodeValue[index] - '0';
1✔
335
        if (codeValue < STATUS_LIST.size()) {
1✔
336
          return STATUS_LIST.get(codeValue);
1✔
337
        }
338
        break;
339
      default:
340
        break;
341
    }
342
    return UNKNOWN.withDescription("Unknown code " + new String(asciiCodeValue, US_ASCII));
×
343
  }
344

345
  /**
346
   * Return a {@link Status} given a canonical error {@link Code} object.
347
   */
348
  public static Status fromCode(Code code) {
349
    return code.toStatus();
1✔
350
  }
351

352
  /**
353
   * Key to bind status code to trailing metadata.
354
   */
355
  static final Metadata.Key<Status> CODE_KEY
1✔
356
      = Metadata.Key.of("grpc-status", false /* not pseudo */, new StatusCodeMarshaller());
1✔
357

358
  /**
359
   * Marshals status messages for ({@link #MESSAGE_KEY}.  gRPC does not use binary coding of
360
   * status messages by default, which makes sending arbitrary strings difficult.  This marshaller
361
   * uses ASCII printable characters by default, and percent encodes (e.g. %0A) all non ASCII bytes.
362
   * This leads to normal text being mostly readable (especially useful for debugging), and special
363
   * text still being sent.
364
   *
365
   * <p>By default, the HTTP spec says that header values must be encoded using a strict subset of
366
   * ASCII (See RFC 7230 section 3.2.6).  HTTP/2 HPACK allows use of arbitrary binary headers, but
367
   * we do not use them for interoperating with existing HTTP/1.1 code.  Since the grpc-message
368
   * is encoded to such a header, it needs to not use forbidden characters.
369
   *
370
   * <p>This marshaller works by converting the passed in string into UTF-8, checking to see if
371
   * each individual byte is an allowable byte, and then either percent encoding or passing it
372
   * through.  When percent encoding, the byte is converted into hexadecimal notation with a '%'
373
   * prepended.
374
   *
375
   * <p>When unmarshalling, bytes are passed through unless they match the "%XX" pattern.  If they
376
   * do match, the unmarshaller attempts to convert them back into their original UTF-8 byte
377
   * sequence.  After the input header bytes are converted into UTF-8 bytes, the new byte array is
378
   * reinterpretted back as a string.
379
   */
380
  private static final TrustedAsciiMarshaller<String> STATUS_MESSAGE_MARSHALLER =
1✔
381
      new StatusMessageMarshaller();
382

383
  /**
384
   * Key to bind status message to trailing metadata.
385
   */
386
  static final Metadata.Key<String> MESSAGE_KEY =
1✔
387
      Metadata.Key.of("grpc-message", false /* not pseudo */, STATUS_MESSAGE_MARSHALLER);
1✔
388

389
  /**
390
   * Extract an error {@link Status} from the causal chain of a {@link Throwable}.
391
   * If no status can be found, a status is created with {@link Code#UNKNOWN} as its code and
392
   * {@code t} as its cause.
393
   *
394
   * @return non-{@code null} status
395
   */
396
  public static Status fromThrowable(Throwable t) {
397
    Throwable cause = checkNotNull(t, "t");
1✔
398
    while (cause != null) {
1✔
399
      if (cause instanceof StatusException) {
1✔
400
        return ((StatusException) cause).getStatus();
1✔
401
      } else if (cause instanceof StatusRuntimeException) {
1✔
402
        return ((StatusRuntimeException) cause).getStatus();
1✔
403
      }
404
      cause = cause.getCause();
1✔
405
    }
406
    // Couldn't find a cause with a Status
407
    return UNKNOWN.withCause(t);
1✔
408
  }
409

410
  /**
411
   * Extract an error trailers from the causal chain of a {@link Throwable}.
412
   *
413
   * @return the trailers or {@code null} if not found.
414
   */
415
  @Nullable
416
  public static Metadata trailersFromThrowable(Throwable t) {
417
    Throwable cause = checkNotNull(t, "t");
1✔
418
    while (cause != null) {
1✔
419
      if (cause instanceof StatusException) {
1✔
420
        return ((StatusException) cause).getTrailers();
1✔
421
      } else if (cause instanceof StatusRuntimeException) {
1✔
422
        return ((StatusRuntimeException) cause).getTrailers();
1✔
423
      }
424
      cause = cause.getCause();
1✔
425
    }
426
    return null;
1✔
427
  }
428

429
  static String formatThrowableMessage(Status status) {
430
    if (status.description == null) {
1✔
431
      return status.code.toString();
1✔
432
    } else {
433
      return status.code + ": " + status.description;
1✔
434
    }
435
  }
436

437
  private final Code code;
438
  private final String description;
439
  private final Throwable cause;
440

441
  private Status(Code code) {
442
    this(code, null, null);
1✔
443
  }
1✔
444

445
  private Status(Code code, @Nullable String description, @Nullable Throwable cause) {
1✔
446
    this.code = checkNotNull(code, "code");
1✔
447
    this.description = description;
1✔
448
    this.cause = cause;
1✔
449
  }
1✔
450

451
  /**
452
   * Create a derived instance of {@link Status} with the given cause.
453
   * However, the cause is not transmitted from server to client.
454
   */
455
  public Status withCause(Throwable cause) {
456
    if (Objects.equal(this.cause, cause)) {
1✔
457
      return this;
1✔
458
    }
459
    return new Status(this.code, this.description, cause);
1✔
460
  }
461

462
  /**
463
   * Create a derived instance of {@link Status} with the given description.  Leading and trailing
464
   * whitespace may be removed; this may change in the future.
465
   */
466
  public Status withDescription(String description) {
467
    if (Objects.equal(this.description, description)) {
1✔
468
      return this;
1✔
469
    }
470
    return new Status(this.code, description, this.cause);
1✔
471
  }
472

473
  /**
474
   * Create a derived instance of {@link Status} augmenting the current description with
475
   * additional detail.  Leading and trailing whitespace may be removed; this may change in the
476
   * future.
477
   */
478
  public Status augmentDescription(String additionalDetail) {
479
    if (additionalDetail == null) {
1✔
480
      return this;
1✔
481
    } else if (this.description == null) {
1✔
482
      return new Status(this.code, additionalDetail, this.cause);
1✔
483
    } else {
484
      return new Status(this.code, this.description + "\n" + additionalDetail, this.cause);
1✔
485
    }
486
  }
487

488
  /**
489
   * The canonical status code.
490
   */
491
  public Code getCode() {
492
    return code;
1✔
493
  }
494

495
  /**
496
   * A description of this status for human consumption.
497
   */
498
  @Nullable
499
  public String getDescription() {
500
    return description;
1✔
501
  }
502

503
  /**
504
   * The underlying cause of an error.
505
   * Note that the cause is not transmitted from server to client.
506
   */
507
  @Nullable
508
  public Throwable getCause() {
509
    return cause;
1✔
510
  }
511

512
  /**
513
   * Is this status OK, i.e., not an error.
514
   */
515
  public boolean isOk() {
516
    return Code.OK == code;
1✔
517
  }
518

519
  /**
520
   * Convert this {@link Status} to a {@link RuntimeException}. Use {@link #fromThrowable}
521
   * to recover this {@link Status} instance when the returned exception is in the causal chain.
522
   */
523
  public StatusRuntimeException asRuntimeException() {
524
    return new StatusRuntimeException(this);
1✔
525
  }
526

527
  /**
528
   * Same as {@link #asRuntimeException()} but includes the provided trailers in the returned
529
   * exception.
530
   */
531
  public StatusRuntimeException asRuntimeException(@Nullable Metadata trailers) {
532
    return new StatusRuntimeException(this, trailers);
1✔
533
  }
534

535
  /**
536
   * Convert this {@link Status} to an {@link Exception}. Use {@link #fromThrowable}
537
   * to recover this {@link Status} instance when the returned exception is in the causal chain.
538
   */
539
  public StatusException asException() {
540
    return new StatusException(this);
1✔
541
  }
542

543
  /**
544
   * Same as {@link #asException()} but includes the provided trailers in the returned exception.
545
   */
546
  public StatusException asException(@Nullable Metadata trailers) {
547
    return new StatusException(this, trailers);
1✔
548
  }
549

550
  /** A string representation of the status useful for debugging. */
551
  @Override
552
  public String toString() {
553
    return MoreObjects.toStringHelper(this)
1✔
554
        .add("code", code.name())
1✔
555
        .add("description", description)
1✔
556
        .add("cause", cause != null ? getStackTraceAsString(cause) : cause)
1✔
557
        .toString();
1✔
558
  }
559

560
  private static final class StatusCodeMarshaller implements TrustedAsciiMarshaller<Status> {
561
    @Override
562
    public byte[] toAsciiString(Status status) {
563
      return status.getCode().valueAscii();
1✔
564
    }
565

566
    @Override
567
    public Status parseAsciiString(byte[] serialized) {
568
      return fromCodeValue(serialized);
1✔
569
    }
570
  }
571

572
  private static final class StatusMessageMarshaller implements TrustedAsciiMarshaller<String> {
573

574
    private static final byte[] HEX =
1✔
575
        {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
576

577
    @Override
578
    public byte[] toAsciiString(String value) {
579
      byte[] valueBytes = value.getBytes(UTF_8);
1✔
580
      for (int i = 0; i < valueBytes.length; i++) {
1✔
581
        byte b = valueBytes[i];
1✔
582
        // If there are only non escaping characters, skip the slow path.
583
        if (isEscapingChar(b)) {
1✔
584
          return toAsciiStringSlow(valueBytes, i);
1✔
585
        }
586
      }
587
      return valueBytes;
1✔
588
    }
589

590
    private static boolean isEscapingChar(byte b) {
591
      return b < ' ' || b >= '~' || b == '%';
1✔
592
    }
593

594
    /**
595
     * Percent encode bytes to make them ASCII.
596
     *
597
     * @param valueBytes the UTF-8 bytes
598
     * @param ri The reader index, pointed at the first byte that needs escaping.
599
     */
600
    private static byte[] toAsciiStringSlow(byte[] valueBytes, int ri) {
601
      byte[] escapedBytes = new byte[ri + (valueBytes.length - ri) * 3];
1✔
602
      // copy over the good bytes
603
      if (ri != 0) {
1✔
604
        System.arraycopy(valueBytes, 0, escapedBytes, 0, ri);
1✔
605
      }
606
      int wi = ri;
1✔
607
      for (; ri < valueBytes.length; ri++) {
1✔
608
        byte b = valueBytes[ri];
1✔
609
        // Manually implement URL encoding, per the gRPC spec.
610
        if (isEscapingChar(b)) {
1✔
611
          escapedBytes[wi] = '%';
1✔
612
          escapedBytes[wi + 1] = HEX[(b >> 4) & 0xF];
1✔
613
          escapedBytes[wi + 2] = HEX[b & 0xF];
1✔
614
          wi += 3;
1✔
615
          continue;
1✔
616
        }
617
        escapedBytes[wi++] = b;
1✔
618
      }
619
      return Arrays.copyOf(escapedBytes, wi);
1✔
620
    }
621

622
    @SuppressWarnings("deprecation") // Use fast but deprecated String ctor
623
    @Override
624
    public String parseAsciiString(byte[] value) {
625
      for (int i = 0; i < value.length; i++) {
1✔
626
        byte b = value[i];
1✔
627
        if (b < ' ' || b >= '~' || (b == '%' && i + 2 < value.length)) {
1✔
628
          return parseAsciiStringSlow(value);
1✔
629
        }
630
      }
631
      return new String(value, 0);
1✔
632
    }
633

634
    private static String parseAsciiStringSlow(byte[] value) {
635
      ByteBuffer buf = ByteBuffer.allocate(value.length);
1✔
636
      for (int i = 0; i < value.length;) {
1✔
637
        if (value[i] == '%' && i + 2 < value.length) {
1✔
638
          try {
639
            buf.put((byte)Integer.parseInt(new String(value, i + 1, 2, US_ASCII), 16));
1✔
640
            i += 3;
1✔
641
            continue;
1✔
642
          } catch (NumberFormatException e) {
1✔
643
            // ignore, fall through, just push the bytes.
644
          }
645
        }
646
        buf.put(value[i]);
1✔
647
        i += 1;
1✔
648
      }
649
      return new String(buf.array(), 0, buf.position(), UTF_8);
1✔
650
    }
651
  }
652

653
  /**
654
   * Equality on Statuses is not well defined.  Instead, do comparison based on their Code with
655
   * {@link #getCode}.  The description and cause of the Status are unlikely to be stable, and
656
   * additional fields may be added to Status in the future.
657
   */
658
  @Override
659
  public boolean equals(Object obj) {
660
    return super.equals(obj);
1✔
661
  }
662

663
  /**
664
   * Hash codes on Statuses are not well defined.
665
   *
666
   * @see #equals
667
   */
668
  @Override
669
  public int hashCode() {
670
    return super.hashCode();
1✔
671
  }
672
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc