Skip to content

Commit

Permalink
Allow setting example paths and queries for DocService (#2546)
Browse files Browse the repository at this point in the history
Motivation:

Currently, a user cannot set example paths and queries. #2195
The endpoint path or queries are only set automatically when
opening a URL that contains `endpoint_path` or `queries` as params.

Modifications:

- Add `examplePaths` to `DocService` and `DocServiceBuilder`
- Add `exampleQueries` to `DocService` and `DocServiceBuilder`
- Show `examplePath` if the `endpoint_path` is null and the `examplePath` is not null
- Show `exampleQuery` if the `queries` is null and the `exampleQuery` is not null
- Make `ENDPOINT PATH` input visible with read-only if a path is exact type 

Result:
You can now set an example path and queries for `DocService`
Fixes #2195
  • Loading branch information
ikhoon committed Mar 10, 2020
1 parent e947c9a commit 721f185
Show file tree
Hide file tree
Showing 13 changed files with 408 additions and 95 deletions.
Expand Up @@ -99,6 +99,8 @@ public static DocServiceBuilder builder() {

private final Map<String, ListMultimap<String, HttpHeaders>> exampleHttpHeaders;
private final Map<String, ListMultimap<String, String>> exampleRequests;
private final Map<String, ListMultimap<String, String>> examplePaths;
private final Map<String, ListMultimap<String, String>> exampleQueries;
private final DocServiceFilter filter;

@Nullable
Expand All @@ -108,14 +110,18 @@ public static DocServiceBuilder builder() {
* Creates a new instance.
*/
public DocService() {
this(ImmutableMap.of(), ImmutableMap.of(), ImmutableList.of(), DocServiceBuilder.ALL_SERVICES);
this(/* exampleHttpHeaders */ ImmutableMap.of(), /* exampleRequests */ ImmutableMap.of(),
/* examplePaths */ ImmutableMap.of(), /* exampleQueries */ ImmutableMap.of(),
/* injectedScriptSuppliers */ ImmutableList.of(), DocServiceBuilder.ALL_SERVICES);
}

/**
* Creates a new instance with example HTTP headers and example requests and injected scripts.
*/
DocService(Map<String, ListMultimap<String, HttpHeaders>> exampleHttpHeaders,
Map<String, ListMultimap<String, String>> exampleRequests,
Map<String, ListMultimap<String, String>> examplePaths,
Map<String, ListMultimap<String, String>> exampleQueries,
List<BiFunction<ServiceRequestContext, HttpRequest, String>> injectedScriptSuppliers,
DocServiceFilter filter) {

Expand All @@ -130,6 +136,8 @@ public DocService() {
"com/linecorp/armeria/server/docs")));
this.exampleHttpHeaders = immutableCopyOf(exampleHttpHeaders, "exampleHttpHeaders");
this.exampleRequests = immutableCopyOf(exampleRequests, "exampleRequests");
this.examplePaths = immutableCopyOf(examplePaths, "examplePaths");
this.exampleQueries = immutableCopyOf(exampleQueries, "exampleQueries");
this.filter = requireNonNull(filter, "filter");
}

Expand Down Expand Up @@ -235,6 +243,8 @@ private static MethodInfo addMethodDocStrings(ServiceInfo service, MethodInfo me
method.endpoints(),
method.exampleHttpHeaders(),
method.exampleRequests(),
method.examplePaths(),
method.exampleQueries(),
method.httpMethod(),
docString(service.name() + '/' + method.name(), method.docString(), docStrings));
}
Expand Down Expand Up @@ -311,6 +321,10 @@ private ServiceInfo addServiceExamples(ServiceInfo service) {
this.exampleHttpHeaders.getOrDefault(service.name(), ImmutableListMultimap.of());
final ListMultimap<String, String> exampleRequests =
this.exampleRequests.getOrDefault(service.name(), ImmutableListMultimap.of());
final ListMultimap<String, String> examplePaths =
this.examplePaths.getOrDefault(service.name(), ImmutableListMultimap.of());
final ListMultimap<String, String> exampleQueries =
this.exampleQueries.getOrDefault(service.name(), ImmutableListMultimap.of());

// Reconstruct ServiceInfo with the examples.
return new ServiceInfo(
Expand All @@ -323,6 +337,8 @@ private ServiceInfo addServiceExamples(ServiceInfo service) {
// generated by the plugin.
concatAndDedup(exampleHttpHeaders.get(m.name()), m.exampleHttpHeaders()),
concatAndDedup(exampleRequests.get(m.name()), m.exampleRequests()),
examplePaths.get(m.name()),
exampleQueries.get(m.name()),
m.httpMethod(), m.docString()))::iterator,
Iterables.concat(service.exampleHttpHeaders(),
exampleHttpHeaders.get("")),
Expand Down
Expand Up @@ -55,6 +55,8 @@ public final class DocServiceBuilder {

private final Map<String, ListMultimap<String, HttpHeaders>> exampleHttpHeaders = new HashMap<>();
private final Map<String, ListMultimap<String, String>> exampleRequests = new HashMap<>();
private final Map<String, ListMultimap<String, String>> examplePaths = new HashMap<>();
private final Map<String, ListMultimap<String, String>> exampleQueries = new HashMap<>();
private final List<BiFunction<ServiceRequestContext, HttpRequest, String>> injectedScriptSuppliers =
new ArrayList<>();

Expand Down Expand Up @@ -177,6 +179,90 @@ private DocServiceBuilder exampleHttpHeaders0(String serviceName, String methodN
return this;
}

/**
* Adds the specified example paths for the method with the specified service and method name.
*/
public DocServiceBuilder examplePaths(Class<?> serviceType, String methodName, String... paths) {
requireNonNull(serviceType, "serviceType");
return examplePaths(serviceType.getName(), methodName, paths);
}

/**
* Adds the specified example paths for the method with the specified service and method name.
*/
public DocServiceBuilder examplePaths(Class<?> serviceType, String methodName, Iterable<String> paths) {
requireNonNull(serviceType, "serviceType");
return examplePaths(serviceType.getName(), methodName, paths);
}

/**
* Adds the specified example paths for the method with the specified service and method name.
*/
public DocServiceBuilder examplePaths(String serviceName, String methodName, String... paths) {
requireNonNull(paths, "paths");
return examplePaths(serviceName, methodName, ImmutableList.copyOf(paths));
}

/**
* Adds the specified example paths for the method with the specified service and method name.
*/
public DocServiceBuilder examplePaths(String serviceName, String methodName, Iterable<String> paths) {
requireNonNull(serviceName, "serviceName");
checkArgument(!serviceName.isEmpty(), "serviceName is empty.");
requireNonNull(methodName, "methodName");
checkArgument(!methodName.isEmpty(), "methodName is empty.");
requireNonNull(paths, "paths");
for (String examplePath : paths) {
requireNonNull(examplePath, "paths contains null");
examplePaths.computeIfAbsent(serviceName, unused -> ArrayListMultimap.create())
.put(methodName, examplePath);
}
return this;
}

/**
* Adds the specified example query strings for the method with the specified service and method name.
*/
public DocServiceBuilder exampleQueries(Class<?> serviceType, String methodName, String... queryStrings) {
requireNonNull(serviceType, "serviceType");
return exampleQueries(serviceType.getName(), methodName, queryStrings);
}

/**
* Adds the specified example query strings for the method with the specified service and method name.
*/
public DocServiceBuilder exampleQueries(Class<?> serviceType, String methodName,
Iterable<String> queryStrings) {
requireNonNull(serviceType, "serviceType");
return exampleQueries(serviceType.getName(), methodName, queryStrings);
}

/**
* Adds the specified example query strings for the method with the specified service and method name.
*/
public DocServiceBuilder exampleQueries(String serviceName, String methodName, String... queryStrings) {
requireNonNull(queryStrings, "queryStrings");
return exampleQueries(serviceName, methodName, ImmutableList.copyOf(queryStrings));
}

/**
* Adds the specified example query strings for the method with the specified service and method name.
*/
public DocServiceBuilder exampleQueries(String serviceName, String methodName,
Iterable<String> queryStrings) {
requireNonNull(serviceName, "serviceName");
checkArgument(!serviceName.isEmpty(), "serviceName is empty.");
requireNonNull(methodName, "methodName");
checkArgument(!methodName.isEmpty(), "methodName is empty.");
requireNonNull(queryStrings, "queryStrings");
for (String query : queryStrings) {
requireNonNull(query, "queryStrings contains null");
exampleQueries.computeIfAbsent(serviceName, unused -> ArrayListMultimap.create())
.put(methodName, query);
}
return this;
}

/**
* Adds the example requests for the method with the specified service type and method name.
* This method is a shortcut to:
Expand Down Expand Up @@ -446,7 +532,7 @@ private static String[] guessAndSerializeExampleRequest(Object exampleRequest) {
* Returns a newly-created {@link DocService} based on the properties of this builder.
*/
public DocService build() {
return new DocService(exampleHttpHeaders, exampleRequests, injectedScriptSuppliers,
unifyFilter(includeFilter, excludeFilter));
return new DocService(exampleHttpHeaders, exampleRequests, examplePaths, exampleQueries,
injectedScriptSuppliers, unifyFilter(includeFilter, excludeFilter));
}
}
Expand Up @@ -16,6 +16,7 @@

package com.linecorp.armeria.server.docs;

import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Comparator.comparing;
import static java.util.Objects.requireNonNull;

Expand All @@ -32,10 +33,12 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;

import com.linecorp.armeria.common.HttpHeaders;
import com.linecorp.armeria.common.HttpMethod;
import com.linecorp.armeria.common.util.UnstableApi;
import com.linecorp.armeria.internal.common.PathAndQuery;
import com.linecorp.armeria.server.Service;

/**
Expand All @@ -53,6 +56,8 @@ public final class MethodInfo {
private final Set<EndpointInfo> endpoints;
private final List<HttpHeaders> exampleHttpHeaders;
private final List<String> exampleRequests;
private final List<String> examplePaths;
private final List<String> exampleQueries;
private final HttpMethod httpMethod;
@Nullable
private final String docString;
Expand All @@ -78,8 +83,10 @@ public MethodInfo(String name,
Iterable<EndpointInfo> endpoints,
HttpMethod httpMethod,
@Nullable String docString) {
this(name, returnTypeSignature, parameters, exceptionTypeSignatures,
endpoints, ImmutableList.of(), ImmutableList.of(), httpMethod, docString);
this(name, returnTypeSignature, parameters, exceptionTypeSignatures, endpoints,
/* exampleHttpHeaders */ ImmutableList.of(), /* exampleRequests */ ImmutableList.of(),
/* examplePaths */ ImmutableList.of(), /* exampleQueries */ ImmutableList.of(),
httpMethod, docString);
}

/**
Expand All @@ -92,6 +99,8 @@ public MethodInfo(String name,
Iterable<EndpointInfo> endpoints,
Iterable<HttpHeaders> exampleHttpHeaders,
Iterable<String> exampleRequests,
Iterable<String> examplePaths,
Iterable<String> exampleQueries,
HttpMethod httpMethod,
@Nullable String docString) {
this.name = requireNonNull(name, "name");
Expand All @@ -108,6 +117,27 @@ public MethodInfo(String name,
this.exampleHttpHeaders = ImmutableList.copyOf(requireNonNull(exampleHttpHeaders,
"exampleHttpHeaders"));
this.exampleRequests = ImmutableList.copyOf(requireNonNull(exampleRequests, "exampleRequests"));

requireNonNull(examplePaths, "examplePaths");
final ImmutableList.Builder<String> examplePathsBuilder =
ImmutableList.builderWithExpectedSize(Iterables.size(examplePaths));
for (String path : examplePaths) {
final PathAndQuery pathAndQuery = PathAndQuery.parse(path);
checkArgument(pathAndQuery != null, "examplePaths contains an invalid path: %s", path);
examplePathsBuilder.add(pathAndQuery.path());
}
this.examplePaths = examplePathsBuilder.build();

requireNonNull(exampleQueries, "exampleQueries");
final ImmutableList.Builder<String> exampleQueriesBuilder =
ImmutableList.builderWithExpectedSize(Iterables.size(exampleQueries));
for (String query : exampleQueries) {
final PathAndQuery pathAndQuery = PathAndQuery.parse('?' + query);
checkArgument(pathAndQuery != null, "exampleQueries contains an invalid query string: %s", query);
exampleQueriesBuilder.add(pathAndQuery.query());
}
this.exampleQueries = exampleQueriesBuilder.build();

this.httpMethod = requireNonNull(httpMethod, "httpMethod");
this.docString = Strings.emptyToNull(docString);
}
Expand Down Expand Up @@ -169,6 +199,22 @@ public List<String> exampleRequests() {
return exampleRequests;
}

/**
* Returns the example paths of the method.
*/
@JsonProperty
public List<String> examplePaths() {
return examplePaths;
}

/**
* Returns the example queries of the method.
*/
@JsonProperty
public List<String> exampleQueries() {
return exampleQueries;
}

/**
* Returns the HTTP method of this method.
*/
Expand Down
Expand Up @@ -123,8 +123,8 @@ static Set<MethodInfo> mergeEndpoints(Iterable<MethodInfo> methodInfos) {
return new MethodInfo(value.name(), value.returnTypeSignature(),
value.parameters(), value.exceptionTypeSignatures(),
endpointInfos, value.exampleHttpHeaders(),
value.exampleRequests(), value.httpMethod(),
value.docString());
value.exampleRequests(), value.examplePaths(), value.exampleQueries(),
value.httpMethod(), value.docString());
}
});
}
Expand Down

0 comments on commit 721f185

Please sign in to comment.