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

grpc / grpc-java / #18902

17 Nov 2023 03:44PM UTC coverage: 88.236% (+0.02%) from 88.221%
#18902

push

github

web-flow
api: Clarify that NameResolver can only be started once (#10681)

30347 of 34393 relevant lines covered (88.24%)

0.88 hits per line

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

69.44
/../api/src/main/java/io/grpc/NameResolver.java
1
/*
2
 * Copyright 2015 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.checkArgument;
20
import static com.google.common.base.Preconditions.checkNotNull;
21

22
import com.google.common.base.MoreObjects;
23
import com.google.common.base.Objects;
24
import com.google.errorprone.annotations.InlineMe;
25
import java.lang.annotation.Documented;
26
import java.lang.annotation.Retention;
27
import java.lang.annotation.RetentionPolicy;
28
import java.net.URI;
29
import java.util.ArrayList;
30
import java.util.Collections;
31
import java.util.List;
32
import java.util.Map;
33
import java.util.concurrent.Executor;
34
import java.util.concurrent.ScheduledExecutorService;
35
import javax.annotation.Nullable;
36
import javax.annotation.concurrent.ThreadSafe;
37

38
/**
39
 * A pluggable component that resolves a target {@link URI} and return addresses to the caller.
40
 *
41
 * <p>A {@code NameResolver} uses the URI's scheme to determine whether it can resolve it, and uses
42
 * the components after the scheme for actual resolution.
43
 *
44
 * <p>The addresses and attributes of a target may be changed over time, thus the caller registers a
45
 * {@link Listener} to receive continuous updates.
46
 *
47
 * <p>A {@code NameResolver} does not need to automatically re-resolve on failure. Instead, the
48
 * {@link Listener} is responsible for eventually (after an appropriate backoff period) invoking
49
 * {@link #refresh()}.
50
 *
51
 * <p>Implementations <strong>don't need to be thread-safe</strong>.  All methods are guaranteed to
52
 * be called sequentially.  Additionally, all methods that have side-effects, i.e.,
53
 * {@link #start(Listener2)}, {@link #shutdown} and {@link #refresh} are called from the same
54
 * {@link SynchronizationContext} as returned by {@link Args#getSynchronizationContext}. <strong>Do
55
 * not block</strong> within the synchronization context; blocking I/O and time-consuming tasks
56
 * should be offloaded to a separate thread, generally {@link Args#getOffloadExecutor}.
57
 *
58
 * @since 1.0.0
59
 */
60
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
61
public abstract class NameResolver {
1✔
62
  /**
63
   * Returns the authority used to authenticate connections to servers.  It <strong>must</strong> be
64
   * from a trusted source, because if the authority is tampered with, RPCs may be sent to the
65
   * attackers which may leak sensitive user data.
66
   *
67
   * <p>An implementation must generate it without blocking, typically in line, and
68
   * <strong>must</strong> keep it unchanged. {@code NameResolver}s created from the same factory
69
   * with the same argument must return the same authority.
70
   *
71
   * @since 1.0.0
72
   */
73
  public abstract String getServiceAuthority();
74

75
  /**
76
   * Starts the resolution. The method is not supposed to throw any exceptions. That might cause the
77
   * Channel that the name resolver is serving to crash. Errors should be propagated
78
   * through {@link Listener#onError}.
79
   * 
80
   * <p>An instance may not be started more than once, by any overload of this method, even after
81
   * an intervening call to {@link #shutdown}.
82
   *
83
   * @param listener used to receive updates on the target
84
   * @since 1.0.0
85
   */
86
  public void start(final Listener listener) {
87
    if (listener instanceof Listener2) {
×
88
      start((Listener2) listener);
×
89
    } else {
90
      start(new Listener2() {
×
91
          @Override
92
          public void onError(Status error) {
93
            listener.onError(error);
×
94
          }
×
95

96
          @Override
97
          public void onResult(ResolutionResult resolutionResult) {
98
            listener.onAddresses(resolutionResult.getAddresses(), resolutionResult.getAttributes());
×
99
          }
×
100
      });
101
    }
102
  }
×
103

104
  /**
105
   * Starts the resolution. The method is not supposed to throw any exceptions. That might cause the
106
   * Channel that the name resolver is serving to crash. Errors should be propagated
107
   * through {@link Listener2#onError}.
108
   * 
109
   * <p>An instance may not be started more than once, by any overload of this method, even after
110
   * an intervening call to {@link #shutdown}.
111
   *
112
   * @param listener used to receive updates on the target
113
   * @since 1.21.0
114
   */
115
  public void start(Listener2 listener) {
116
    start((Listener) listener);
×
117
  }
×
118

119
  /**
120
   * Stops the resolution. Updates to the Listener will stop.
121
   *
122
   * @since 1.0.0
123
   */
124
  public abstract void shutdown();
125

126
  /**
127
   * Re-resolve the name.
128
   *
129
   * <p>Can only be called after {@link #start} has been called.
130
   *
131
   * <p>This is only a hint. Implementation takes it as a signal but may not start resolution
132
   * immediately. It should never throw.
133
   *
134
   * <p>The default implementation is no-op.
135
   *
136
   * @since 1.0.0
137
   */
138
  public void refresh() {}
1✔
139

140
  /**
141
   * Factory that creates {@link NameResolver} instances.
142
   *
143
   * @since 1.0.0
144
   */
145
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
146
  public abstract static class Factory {
1✔
147
    /**
148
     * Creates a {@link NameResolver} for the given target URI, or {@code null} if the given URI
149
     * cannot be resolved by this factory. The decision should be solely based on the scheme of the
150
     * URI.
151
     *
152
     * @param targetUri the target URI to be resolved, whose scheme must not be {@code null}
153
     * @param args other information that may be useful
154
     *
155
     * @since 1.21.0
156
     */
157
    public abstract NameResolver newNameResolver(URI targetUri, final Args args);
158

159
    /**
160
     * Returns the default scheme, which will be used to construct a URI when {@link
161
     * ManagedChannelBuilder#forTarget(String)} is given an authority string instead of a compliant
162
     * URI.
163
     *
164
     * @since 1.0.0
165
     */
166
    public abstract String getDefaultScheme();
167
  }
168

169
  /**
170
   * Receives address updates.
171
   *
172
   * <p>All methods are expected to return quickly.
173
   *
174
   * @since 1.0.0
175
   */
176
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
177
  @ThreadSafe
178
  public interface Listener {
179
    /**
180
     * Handles updates on resolved addresses and attributes.
181
     *
182
     * <p>Implementations will not modify the given {@code servers}.
183
     *
184
     * @param servers the resolved server addresses. An empty list will trigger {@link #onError}
185
     * @param attributes extra information from naming system.
186
     * @since 1.3.0
187
     */
188
    void onAddresses(
189
        List<EquivalentAddressGroup> servers, @ResolutionResultAttr Attributes attributes);
190

191
    /**
192
     * Handles an error from the resolver. The listener is responsible for eventually invoking
193
     * {@link #refresh()} to re-attempt resolution.
194
     *
195
     * @param error a non-OK status
196
     * @since 1.0.0
197
     */
198
    void onError(Status error);
199
  }
200

201
  /**
202
   * Receives address updates.
203
   *
204
   * <p>All methods are expected to return quickly.
205
   *
206
   * <p>This is a replacement API of {@code Listener}. However, we think this new API may change
207
   * again, so we aren't yet encouraging mass-migration to it. It is fine to use and works.
208
   *
209
   * @since 1.21.0
210
   */
211
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
212
  public abstract static class Listener2 implements Listener {
1✔
213
    /**
214
     * Handles updates on resolved addresses and attributes.
215
     *
216
     * @deprecated This will be removed in 1.22.0
217
     */
218
    @Override
219
    @Deprecated
220
    @InlineMe(
221
        replacement = "this.onResult(ResolutionResult.newBuilder().setAddresses(servers)"
222
            + ".setAttributes(attributes).build())",
223
        imports = "io.grpc.NameResolver.ResolutionResult")
224
    public final void onAddresses(
225
        List<EquivalentAddressGroup> servers, @ResolutionResultAttr Attributes attributes) {
226
      // TODO(jihuncho) need to promote Listener2 if we want to use ConfigOrError
227
      onResult(
×
228
          ResolutionResult.newBuilder().setAddresses(servers).setAttributes(attributes).build());
×
229
    }
×
230

231
    /**
232
     * Handles updates on resolved addresses and attributes.  If
233
     * {@link ResolutionResult#getAddresses()} is empty, {@link #onError(Status)} will be called.
234
     *
235
     * @param resolutionResult the resolved server addresses, attributes, and Service Config.
236
     * @since 1.21.0
237
     */
238
    public abstract void onResult(ResolutionResult resolutionResult);
239

240
    /**
241
     * Handles a name resolving error from the resolver. The listener is responsible for eventually
242
     * invoking {@link NameResolver#refresh()} to re-attempt resolution.
243
     *
244
     * @param error a non-OK status
245
     * @since 1.21.0
246
     */
247
    @Override
248
    public abstract void onError(Status error);
249
  }
250

251
  /**
252
   * Annotation for name resolution result attributes. It follows the annotation semantics defined
253
   * by {@link Attributes}.
254
   */
255
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4972")
256
  @Retention(RetentionPolicy.SOURCE)
257
  @Documented
258
  public @interface ResolutionResultAttr {}
259

260
  /**
261
   * Information that a {@link Factory} uses to create a {@link NameResolver}.
262
   *
263
   * <p>Note this class doesn't override neither {@code equals()} nor {@code hashCode()}.
264
   *
265
   * @since 1.21.0
266
   */
267
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
268
  public static final class Args {
269
    private final int defaultPort;
270
    private final ProxyDetector proxyDetector;
271
    private final SynchronizationContext syncContext;
272
    private final ServiceConfigParser serviceConfigParser;
273
    @Nullable private final ScheduledExecutorService scheduledExecutorService;
274
    @Nullable private final ChannelLogger channelLogger;
275
    @Nullable private final Executor executor;
276
    @Nullable private final String overrideAuthority;
277

278
    private Args(
279
        Integer defaultPort,
280
        ProxyDetector proxyDetector,
281
        SynchronizationContext syncContext,
282
        ServiceConfigParser serviceConfigParser,
283
        @Nullable ScheduledExecutorService scheduledExecutorService,
284
        @Nullable ChannelLogger channelLogger,
285
        @Nullable Executor executor,
286
        @Nullable String overrideAuthority) {
1✔
287
      this.defaultPort = checkNotNull(defaultPort, "defaultPort not set");
1✔
288
      this.proxyDetector = checkNotNull(proxyDetector, "proxyDetector not set");
1✔
289
      this.syncContext = checkNotNull(syncContext, "syncContext not set");
1✔
290
      this.serviceConfigParser = checkNotNull(serviceConfigParser, "serviceConfigParser not set");
1✔
291
      this.scheduledExecutorService = scheduledExecutorService;
1✔
292
      this.channelLogger = channelLogger;
1✔
293
      this.executor = executor;
1✔
294
      this.overrideAuthority = overrideAuthority;
1✔
295
    }
1✔
296

297
    /**
298
     * The port number used in case the target or the underlying naming system doesn't provide a
299
     * port number.
300
     *
301
     * @since 1.21.0
302
     */
303
    public int getDefaultPort() {
304
      return defaultPort;
1✔
305
    }
306

307
    /**
308
     * If the NameResolver wants to support proxy, it should inquire this {@link ProxyDetector}.
309
     * See documentation on {@link ProxyDetector} about how proxies work in gRPC.
310
     *
311
     * @since 1.21.0
312
     */
313
    public ProxyDetector getProxyDetector() {
314
      return proxyDetector;
1✔
315
    }
316

317
    /**
318
     * Returns the {@link SynchronizationContext} where {@link #start(Listener2)}, {@link #shutdown}
319
     * and {@link #refresh} are run from.
320
     *
321
     * @since 1.21.0
322
     */
323
    public SynchronizationContext getSynchronizationContext() {
324
      return syncContext;
1✔
325
    }
326

327
    /**
328
     * Returns a {@link ScheduledExecutorService} for scheduling delayed tasks.
329
     *
330
     * <p>This service is a shared resource and is only meant for quick tasks. DO NOT block or run
331
     * time-consuming tasks.
332
     *
333
     * <p>The returned service doesn't support {@link ScheduledExecutorService#shutdown shutdown()}
334
     *  and {@link ScheduledExecutorService#shutdownNow shutdownNow()}. They will throw if called.
335
     *
336
     * @since 1.26.0
337
     */
338
    @ExperimentalApi("https://github.com/grpc/grpc-java/issues/6454")
339
    public ScheduledExecutorService getScheduledExecutorService() {
340
      if (scheduledExecutorService == null) {
1✔
341
        throw new IllegalStateException("ScheduledExecutorService not set in Builder");
×
342
      }
343
      return scheduledExecutorService;
1✔
344
    }
345

346
    /**
347
     * Returns the {@link ServiceConfigParser}.
348
     *
349
     * @since 1.21.0
350
     */
351
    public ServiceConfigParser getServiceConfigParser() {
352
      return serviceConfigParser;
1✔
353
    }
354

355
    /**
356
     * Returns the {@link ChannelLogger} for the Channel served by this NameResolver.
357
     *
358
     * @since 1.26.0
359
     */
360
    @ExperimentalApi("https://github.com/grpc/grpc-java/issues/6438")
361
    public ChannelLogger getChannelLogger() {
362
      if (channelLogger == null) {
1✔
363
        throw new IllegalStateException("ChannelLogger is not set in Builder");
×
364
      }
365
      return channelLogger;
1✔
366
    }
367

368
    /**
369
     * Returns the Executor on which this resolver should execute long-running or I/O bound work.
370
     * Null if no Executor was set.
371
     *
372
     * @since 1.25.0
373
     */
374
    @Nullable
375
    public Executor getOffloadExecutor() {
376
      return executor;
1✔
377
    }
378

379
    /**
380
     * Returns the overrideAuthority from channel {@link ManagedChannelBuilder#overrideAuthority}.
381
     * Overrides the host name for L7 HTTP virtual host matching. Almost all name resolvers should
382
     * not use this.
383
     *
384
     * @since 1.49.0
385
     */
386
    @Nullable
387
    @ExperimentalApi("https://github.com/grpc/grpc-java/issues/9406")
388
    public String getOverrideAuthority() {
389
      return overrideAuthority;
1✔
390
    }
391

392

393
    @Override
394
    public String toString() {
395
      return MoreObjects.toStringHelper(this)
×
396
          .add("defaultPort", defaultPort)
×
397
          .add("proxyDetector", proxyDetector)
×
398
          .add("syncContext", syncContext)
×
399
          .add("serviceConfigParser", serviceConfigParser)
×
400
          .add("scheduledExecutorService", scheduledExecutorService)
×
401
          .add("channelLogger", channelLogger)
×
402
          .add("executor", executor)
×
403
          .add("overrideAuthority", overrideAuthority)
×
404
          .toString();
×
405
    }
406

407
    /**
408
     * Returns a builder with the same initial values as this object.
409
     *
410
     * @since 1.21.0
411
     */
412
    public Builder toBuilder() {
413
      Builder builder = new Builder();
1✔
414
      builder.setDefaultPort(defaultPort);
1✔
415
      builder.setProxyDetector(proxyDetector);
1✔
416
      builder.setSynchronizationContext(syncContext);
1✔
417
      builder.setServiceConfigParser(serviceConfigParser);
1✔
418
      builder.setScheduledExecutorService(scheduledExecutorService);
1✔
419
      builder.setChannelLogger(channelLogger);
1✔
420
      builder.setOffloadExecutor(executor);
1✔
421
      builder.setOverrideAuthority(overrideAuthority);
1✔
422
      return builder;
1✔
423
    }
424

425
    /**
426
     * Creates a new builder.
427
     *
428
     * @since 1.21.0
429
     */
430
    public static Builder newBuilder() {
431
      return new Builder();
1✔
432
    }
433

434
    /**
435
     * Builder for {@link Args}.
436
     *
437
     * @since 1.21.0
438
     */
439
    public static final class Builder {
440
      private Integer defaultPort;
441
      private ProxyDetector proxyDetector;
442
      private SynchronizationContext syncContext;
443
      private ServiceConfigParser serviceConfigParser;
444
      private ScheduledExecutorService scheduledExecutorService;
445
      private ChannelLogger channelLogger;
446
      private Executor executor;
447
      private String overrideAuthority;
448

449
      Builder() {
1✔
450
      }
1✔
451

452
      /**
453
       * See {@link Args#getDefaultPort}.  This is a required field.
454
       *
455
       * @since 1.21.0
456
       */
457
      public Builder setDefaultPort(int defaultPort) {
458
        this.defaultPort = defaultPort;
1✔
459
        return this;
1✔
460
      }
461

462
      /**
463
       * See {@link Args#getProxyDetector}.  This is required field.
464
       *
465
       * @since 1.21.0
466
       */
467
      public Builder setProxyDetector(ProxyDetector proxyDetector) {
468
        this.proxyDetector = checkNotNull(proxyDetector);
1✔
469
        return this;
1✔
470
      }
471

472
      /**
473
       * See {@link Args#getSynchronizationContext}.  This is a required field.
474
       *
475
       * @since 1.21.0
476
       */
477
      public Builder setSynchronizationContext(SynchronizationContext syncContext) {
478
        this.syncContext = checkNotNull(syncContext);
1✔
479
        return this;
1✔
480
      }
481

482
      /**
483
       * See {@link Args#getScheduledExecutorService}.
484
       */
485
      @ExperimentalApi("https://github.com/grpc/grpc-java/issues/6454")
486
      public Builder setScheduledExecutorService(
487
          ScheduledExecutorService scheduledExecutorService) {
488
        this.scheduledExecutorService = checkNotNull(scheduledExecutorService);
1✔
489
        return this;
1✔
490
      }
491

492
      /**
493
       * See {@link Args#getServiceConfigParser}.  This is a required field.
494
       *
495
       * @since 1.21.0
496
       */
497
      public Builder setServiceConfigParser(ServiceConfigParser parser) {
498
        this.serviceConfigParser = checkNotNull(parser);
1✔
499
        return this;
1✔
500
      }
501

502
      /**
503
       * See {@link Args#getChannelLogger}.
504
       *
505
       * @since 1.26.0
506
       */
507
      @ExperimentalApi("https://github.com/grpc/grpc-java/issues/6438")
508
      public Builder setChannelLogger(ChannelLogger channelLogger) {
509
        this.channelLogger = checkNotNull(channelLogger);
1✔
510
        return this;
1✔
511
      }
512

513
      /**
514
       * See {@link Args#getOffloadExecutor}. This is an optional field.
515
       *
516
       * @since 1.25.0
517
       */
518
      public Builder setOffloadExecutor(Executor executor) {
519
        this.executor = executor;
1✔
520
        return this;
1✔
521
      }
522

523
      /**
524
       * See {@link Args#getOverrideAuthority()}. This is an optional field.
525
       *
526
       * @since 1.49.0
527
       */
528
      @ExperimentalApi("https://github.com/grpc/grpc-java/issues/9406")
529
      public Builder setOverrideAuthority(String authority) {
530
        this.overrideAuthority = authority;
1✔
531
        return this;
1✔
532
      }
533

534
      /**
535
       * Builds an {@link Args}.
536
       *
537
       * @since 1.21.0
538
       */
539
      public Args build() {
540
        return
1✔
541
            new Args(
542
                defaultPort, proxyDetector, syncContext, serviceConfigParser,
543
                scheduledExecutorService, channelLogger, executor, overrideAuthority);
544
      }
545
    }
546
  }
547

548
  /**
549
   * Parses and validates service configuration.
550
   *
551
   * @since 1.21.0
552
   */
553
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
554
  public abstract static class ServiceConfigParser {
1✔
555
    /**
556
     * Parses and validates the service configuration chosen by the name resolver.  This will
557
     * return a {@link ConfigOrError} which contains either the successfully parsed config, or the
558
     * {@link Status} representing the failure to parse.  Implementations are expected to not throw
559
     * exceptions but return a Status representing the failure.  The value inside the
560
     * {@link ConfigOrError} should implement {@code equals()} and {@code hashCode()}.
561
     *
562
     * @param rawServiceConfig The {@link Map} representation of the service config
563
     * @return a tuple of the fully parsed and validated channel configuration, else the Status.
564
     * @since 1.21.0
565
     */
566
    public abstract ConfigOrError parseServiceConfig(Map<String, ?> rawServiceConfig);
567
  }
568

569
  /**
570
   * Represents the results from a Name Resolver.
571
   *
572
   * @since 1.21.0
573
   */
574
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
575
  public static final class ResolutionResult {
576
    private final List<EquivalentAddressGroup> addresses;
577
    @ResolutionResultAttr
578
    private final Attributes attributes;
579
    @Nullable
580
    private final ConfigOrError serviceConfig;
581

582
    ResolutionResult(
583
        List<EquivalentAddressGroup> addresses,
584
        @ResolutionResultAttr Attributes attributes,
585
        ConfigOrError serviceConfig) {
1✔
586
      this.addresses = Collections.unmodifiableList(new ArrayList<>(addresses));
1✔
587
      this.attributes = checkNotNull(attributes, "attributes");
1✔
588
      this.serviceConfig = serviceConfig;
1✔
589
    }
1✔
590

591
    /**
592
     * Constructs a new builder of a name resolution result.
593
     *
594
     * @since 1.21.0
595
     */
596
    public static Builder newBuilder() {
597
      return new Builder();
1✔
598
    }
599

600
    /**
601
     * Converts these results back to a builder.
602
     *
603
     * @since 1.21.0
604
     */
605
    public Builder toBuilder() {
606
      return newBuilder()
1✔
607
          .setAddresses(addresses)
1✔
608
          .setAttributes(attributes)
1✔
609
          .setServiceConfig(serviceConfig);
1✔
610
    }
611

612
    /**
613
     * Gets the addresses resolved by name resolution.
614
     *
615
     * @since 1.21.0
616
     */
617
    public List<EquivalentAddressGroup> getAddresses() {
618
      return addresses;
1✔
619
    }
620

621
    /**
622
     * Gets the attributes associated with the addresses resolved by name resolution.  If there are
623
     * no attributes, {@link Attributes#EMPTY} will be returned.
624
     *
625
     * @since 1.21.0
626
     */
627
    @ResolutionResultAttr
628
    public Attributes getAttributes() {
629
      return attributes;
1✔
630
    }
631

632
    /**
633
     * Gets the Service Config parsed by {@link Args#getServiceConfigParser}.
634
     *
635
     * @since 1.21.0
636
     */
637
    @Nullable
638
    public ConfigOrError getServiceConfig() {
639
      return serviceConfig;
1✔
640
    }
641

642
    @Override
643
    public String toString() {
644
      return MoreObjects.toStringHelper(this)
×
645
          .add("addresses", addresses)
×
646
          .add("attributes", attributes)
×
647
          .add("serviceConfig", serviceConfig)
×
648
          .toString();
×
649
    }
650

651
    /**
652
     * Useful for testing.  May be slow to calculate.
653
     */
654
    @Override
655
    public boolean equals(Object obj) {
656
      if (!(obj instanceof ResolutionResult)) {
×
657
        return false;
×
658
      }
659
      ResolutionResult that = (ResolutionResult) obj;
×
660
      return Objects.equal(this.addresses, that.addresses)
×
661
          && Objects.equal(this.attributes, that.attributes)
×
662
          && Objects.equal(this.serviceConfig, that.serviceConfig);
×
663
    }
664

665
    /**
666
     * Useful for testing.  May be slow to calculate.
667
     */
668
    @Override
669
    public int hashCode() {
670
      return Objects.hashCode(addresses, attributes, serviceConfig);
×
671
    }
672

673
    /**
674
     * A builder for {@link ResolutionResult}.
675
     *
676
     * @since 1.21.0
677
     */
678
    @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
679
    public static final class Builder {
680
      private List<EquivalentAddressGroup> addresses = Collections.emptyList();
1✔
681
      private Attributes attributes = Attributes.EMPTY;
1✔
682
      @Nullable
683
      private ConfigOrError serviceConfig;
684
      //  Make sure to update #toBuilder above!
685

686
      Builder() {}
1✔
687

688
      /**
689
       * Sets the addresses resolved by name resolution.  This field is required.
690
       *
691
       * @since 1.21.0
692
       */
693
      public Builder setAddresses(List<EquivalentAddressGroup> addresses) {
694
        this.addresses = addresses;
1✔
695
        return this;
1✔
696
      }
697

698
      /**
699
       * Sets the attributes for the addresses resolved by name resolution.  If unset,
700
       * {@link Attributes#EMPTY} will be used as a default.
701
       *
702
       * @since 1.21.0
703
       */
704
      public Builder setAttributes(Attributes attributes) {
705
        this.attributes = attributes;
1✔
706
        return this;
1✔
707
      }
708

709
      /**
710
       * Sets the Service Config parsed by {@link Args#getServiceConfigParser}.
711
       * This field is optional.
712
       *
713
       * @since 1.21.0
714
       */
715
      public Builder setServiceConfig(@Nullable ConfigOrError serviceConfig) {
716
        this.serviceConfig = serviceConfig;
1✔
717
        return this;
1✔
718
      }
719

720
      /**
721
       * Constructs a new {@link ResolutionResult} from this builder.
722
       *
723
       * @since 1.21.0
724
       */
725
      public ResolutionResult build() {
726
        return new ResolutionResult(addresses, attributes, serviceConfig);
1✔
727
      }
728
    }
729
  }
730

731
  /**
732
   * Represents either a successfully parsed service config, containing all necessary parts to be
733
   * later applied by the channel, or a Status containing the error encountered while parsing.
734
   *
735
   * @since 1.20.0
736
   */
737
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
1✔
738
  public static final class ConfigOrError {
739

740
    /**
741
     * Returns a {@link ConfigOrError} for the successfully parsed config.
742
     */
743
    public static ConfigOrError fromConfig(Object config) {
744
      return new ConfigOrError(config);
1✔
745
    }
746

747
    /**
748
     * Returns a {@link ConfigOrError} for the failure to parse the config.
749
     *
750
     * @param status a non-OK status
751
     */
752
    public static ConfigOrError fromError(Status status) {
753
      return new ConfigOrError(status);
1✔
754
    }
755

756
    private final Status status;
757
    private final Object config;
758

759
    private ConfigOrError(Object config) {
1✔
760
      this.config = checkNotNull(config, "config");
1✔
761
      this.status = null;
1✔
762
    }
1✔
763

764
    private ConfigOrError(Status status) {
1✔
765
      this.config = null;
1✔
766
      this.status = checkNotNull(status, "status");
1✔
767
      checkArgument(!status.isOk(), "cannot use OK status: %s", status);
1✔
768
    }
1✔
769

770
    /**
771
     * Returns config if exists, otherwise null.
772
     */
773
    @Nullable
774
    public Object getConfig() {
775
      return config;
1✔
776
    }
777

778
    /**
779
     * Returns error status if exists, otherwise null.
780
     */
781
    @Nullable
782
    public Status getError() {
783
      return status;
1✔
784
    }
785

786
    @Override
787
    public boolean equals(Object o) {
788
      if (this == o) {
1✔
789
        return true;
×
790
      }
791
      if (o == null || getClass() != o.getClass()) {
1✔
792
        return false;
×
793
      }
794
      ConfigOrError that = (ConfigOrError) o;
1✔
795
      return Objects.equal(status, that.status) && Objects.equal(config, that.config);
1✔
796
    }
797

798
    @Override
799
    public int hashCode() {
800
      return Objects.hashCode(status, config);
×
801
    }
802

803
    @Override
804
    public String toString() {
805
      if (config != null) {
1✔
806
        return MoreObjects.toStringHelper(this)
1✔
807
            .add("config", config)
1✔
808
            .toString();
1✔
809
      } else {
810
        assert status != null;
×
811
        return MoreObjects.toStringHelper(this)
×
812
            .add("error", status)
×
813
            .toString();
×
814
      }
815
    }
816
  }
817
}
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