ThriftDocServicePlugin implements DocServicePlugin { @Override public ServiceSpecification generateSpecification(Set<ServiceConfig> serviceConfigs, DocServiceFilter filter) { requireNonNull(serviceConfigs, "serviceConfigs"); requireNonNull(filter, "filter"); final Map<Class<?>, EntryBuilder> map = new LinkedHashMap<>(); for (ServiceConfig c : serviceConfigs) { final THttpService service = c.service().as(THttpService.class); assert service != null; service.entries().forEach((serviceName, entry) -> { for (Class<?> iface : entry.interfaces()) { final Class<?> serviceClass = iface.getEnclosingClass(); final EntryBuilder builder = map.computeIfAbsent(serviceClass, cls -> new EntryBuilder(serviceClass)); final Route route = c.route(); final RoutePathType pathType = route.pathType(); if (pathType == RoutePathType.EXACT || pathType == RoutePathType.PREFIX) { builder.endpoint( EndpointInfo.builder(c.virtualHost().hostnamePattern(), route.paths().get(0)) .fragment(serviceName) .defaultFormat(service.defaultSerializationFormat()) .availableFormats(service.supportedSerializationFormats()) .build()); } } }); } final List<Entry> entries = map.values().stream() .map(EntryBuilder::build) .collect(Collectors.toList()); return generate(entries, filter); }  @Override String name(); @Override Set<Class<? extends Service<?, ?>>> supportedServiceTypes(); @Override ServiceSpecification generateSpecification(Set<ServiceConfig> serviceConfigs,
                                                      DocServiceFilter filter); @VisibleForTesting ServiceSpecification generate(List<Entry> entries, DocServiceFilter filter); @Override Map<String, String> loadDocStrings(Set<ServiceConfig> serviceConfigs); @Override Set<Class<?>> supportedExampleRequestTypes(); @Override String guessServiceName(Object exampleRequest); @Override String guessServiceMethodName(Object exampleRequest); @Override String serializeExampleRequest(String serviceName, String methodName,
                                          Object exampleRequest);  }
@Test public void multiplePaths() { final Server server = Server.builder() .service(Route.builder() .exact("/foo1") .build(), THttpService.ofFormats(mock(FooService.AsyncIface.class), ThriftSerializationFormats.COMPACT)) .service(Route.builder() .exact("/foo2") .build(), THttpService.ofFormats(mock(FooService.AsyncIface.class), ThriftSerializationFormats.COMPACT)) .build(); final ServiceSpecification specification = generator.generateSpecification( ImmutableSet.copyOf(server.serviceConfigs()), unifyFilter((plugin, service, method) -> true, (plugin, service, method) -> false)); final ServiceInfo fooServiceInfo = specification.services().iterator().next(); final Map<String, MethodInfo> methods = fooServiceInfo.methods().stream() .collect(toImmutableMap(MethodInfo::name, Function.identity())); final MethodInfo bar4 = methods.get("bar4"); assertThat(bar4.endpoints()) .containsExactlyInAnyOrder( EndpointInfo.builder("*", "/foo1") .defaultFormat(ThriftSerializationFormats.COMPACT) .build(), EndpointInfo.builder("*", "/foo2") .defaultFormat(ThriftSerializationFormats.COMPACT) .build()); }