Skip to content

Commit

Permalink
Display JSON and protobuf request and response specification in `DocS…
Browse files Browse the repository at this point in the history
…ervice` (#4322)

Motivation:

A `@RequestObject` bean can be converted as a specification of an
annotated service. Neither response nor POJO request are shown in a
`DocService`. Only gRPC and Thrift `DocService` can display the full
specifications of input and output types.

This PR aims to generalize a way to create a `StructInfo` from a range
of types by offering `NamedTypeInfoProvider` which can be dynamically
loaded via SPI or explicitly set using `DocServiceBuilder`.
A data type that depends on a specific library, such as protobuf,
Thrift, ScalaPB can extend this interface and inject custom
implementations.

Modifications:

- Designed `NamedTypeInfoProvider` interface to let users customize how to
  create a `StructInfo` from the given type descriptor.
  The following implementations are added for the default behavior.
  - `JsonNamedTypeInfoProvider`
  - `ThriftNamedTypeInfoProvider`
  - `ProtobufNamedTypeInfoProvider`
  - `ScalaPbNamedTypeInfoProvider`
- Add `DocServiceBuilder.nameTypeInfoProvider()` in order to override
  the default `NamedTypeInfoProvider`s.
- Moved some code related to `StructInfo` in `*DocServicePlugin` to
  `*NamedTypeInfoProvider`

Result:

- You can now see the specification of an annotated service made of a JSON
  object or protobuf can be viewed in `DocService`.
- Fixes #4309
  • Loading branch information
ikhoon authored Sep 13, 2022
1 parent 3bb8549 commit 755c282
Show file tree
Hide file tree
Showing 67 changed files with 5,460 additions and 964 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -585,15 +585,14 @@ private static <T extends Annotation, R> Builder<R> getAnnotatedInstances(
/**
* Returns the description of the specified {@link AnnotatedElement}.
*/
@Nullable
static DescriptionInfo findDescription(AnnotatedElement annotatedElement) {
requireNonNull(annotatedElement, "annotatedElement");
final Description description = AnnotationUtil.findFirst(annotatedElement, Description.class);
if (description != null) {
final String value = description.value();
if (DefaultValues.isSpecified(value)) {
checkArgument(!value.isEmpty(), "value is empty.");
return DescriptionInfo.of(description.value(), description.markup());
return DescriptionInfo.from(description);
}
} else if (annotatedElement instanceof Parameter) {
// JavaDoc/KDoc descriptions only exist for method parameters
Expand All @@ -605,24 +604,24 @@ static DescriptionInfo findDescription(AnnotatedElement annotatedElement) {
final Properties cachedProperties = DOCUMENTATION_PROPERTIES_CACHE.getIfPresent(fileName);
if (cachedProperties != null) {
final String propertyValue = cachedProperties.getProperty(propertyName);
return propertyValue != null ? DescriptionInfo.of(propertyValue) : null;
return propertyValue != null ? DescriptionInfo.of(propertyValue) : DescriptionInfo.empty();
}
try (InputStream stream = AnnotatedServiceFactory.class.getClassLoader()
.getResourceAsStream(fileName)) {
if (stream == null) {
return null;
return DescriptionInfo.empty();
}
final Properties properties = new Properties();
properties.load(stream);
DOCUMENTATION_PROPERTIES_CACHE.put(fileName, properties);

final String propertyValue = properties.getProperty(propertyName);
return propertyValue != null ? DescriptionInfo.of(propertyValue) : null;
return propertyValue != null ? DescriptionInfo.of(propertyValue) : DescriptionInfo.empty();
} catch (IOException exception) {
logger.warn("Failed to load an API description file: {}", fileName, exception);
}
}
return null;
return DescriptionInfo.empty();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ private static void warnOnDuplicateResolver(Executable constructorOrMethod,
private static AnnotatedValueResolver ofPathVariable(String name,
AnnotatedElement annotatedElement,
AnnotatedElement typeElement, Class<?> type,
@Nullable DescriptionInfo description) {
DescriptionInfo description) {
return new Builder(annotatedElement, type)
.annotationType(Param.class)
.httpElementName(name)
Expand All @@ -550,7 +550,7 @@ private static AnnotatedValueResolver ofPathVariable(String name,
private static AnnotatedValueResolver ofQueryParam(String name,
AnnotatedElement annotatedElement,
AnnotatedElement typeElement, Class<?> type,
@Nullable DescriptionInfo description,
DescriptionInfo description,
@Nullable String serviceQueryDelimiter) {
String queryDelimiter = serviceQueryDelimiter;
final Delimiter delimiter = annotatedElement.getAnnotation(Delimiter.class);
Expand All @@ -576,7 +576,7 @@ private static AnnotatedValueResolver ofQueryParam(String name,
private static AnnotatedValueResolver ofFileParam(String name,
AnnotatedElement annotatedElement,
AnnotatedElement typeElement, Class<?> type,
@Nullable DescriptionInfo description) {
DescriptionInfo description) {
return new Builder(annotatedElement, type)
.annotationType(Param.class)
.httpElementName(name)
Expand All @@ -591,7 +591,7 @@ private static AnnotatedValueResolver ofFileParam(String name,
private static AnnotatedValueResolver ofHeader(String name,
AnnotatedElement annotatedElement,
AnnotatedElement typeElement, Class<?> type,
@Nullable DescriptionInfo description) {
DescriptionInfo description) {
return new Builder(annotatedElement, type)
.annotationType(Header.class)
.httpElementName(name)
Expand All @@ -609,7 +609,7 @@ private static AnnotatedValueResolver ofRequestObject(AnnotatedElement annotated
Class<?> type, Set<String> pathParams,
List<RequestObjectResolver> objectResolvers,
DependencyInjector dependencyInjector,
@Nullable DescriptionInfo description) {
DescriptionInfo description) {
// To do recursive resolution like a bean inside another bean, the original object resolvers should
// be passed into the AnnotatedBeanFactoryRegistry#register.
final BeanFactoryId beanFactoryId = AnnotatedBeanFactoryRegistry.register(
Expand Down Expand Up @@ -874,6 +874,16 @@ private static Type parameterizedTypeOf(AnnotatedElement element) {
element.getClass().getSimpleName());
}

static boolean isAnnotatedNullable(AnnotatedElement annotatedElement) {
for (Annotation a : annotatedElement.getAnnotations()) {
final String annotationTypeName = a.annotationType().getName();
if (annotationTypeName.endsWith(".Nullable")) {
return true;
}
}
return false;
}

@Nullable
private final Class<? extends Annotation> annotationType;

Expand All @@ -893,7 +903,6 @@ private static Type parameterizedTypeOf(AnnotatedElement element) {
@Nullable
private final Object defaultValue;

@Nullable
private final DescriptionInfo description;

private final BiFunction<AnnotatedValueResolver, ResolverContext, Object> resolver;
Expand All @@ -913,7 +922,7 @@ private AnnotatedValueResolver(@Nullable Class<? extends Annotation> annotationT
@Nullable Class<?> containerType, Class<?> elementType,
@Nullable ParameterizedType parameterizedElementType,
@Nullable String defaultValue,
@Nullable DescriptionInfo description,
DescriptionInfo description,
BiFunction<AnnotatedValueResolver, ResolverContext, Object> resolver,
@Nullable BeanFactoryId beanFactoryId,
AggregationStrategy aggregationStrategy) {
Expand All @@ -924,7 +933,7 @@ private AnnotatedValueResolver(@Nullable Class<? extends Annotation> annotationT
this.shouldWrapValueAsOptional = shouldWrapValueAsOptional;
this.elementType = requireNonNull(elementType, "elementType");
this.parameterizedElementType = parameterizedElementType;
this.description = description;
this.description = requireNonNull(description, "description");
this.containerType = containerType;
this.resolver = requireNonNull(resolver, "resolver");
this.beanFactoryId = beanFactoryId;
Expand Down Expand Up @@ -988,7 +997,6 @@ Object defaultValue() {
return defaultValue;
}

@Nullable
DescriptionInfo description() {
return description;
}
Expand Down Expand Up @@ -1068,8 +1076,7 @@ private static final class Builder {
private boolean pathVariable;
private boolean supportContainer;
private boolean supportDefault;
@Nullable
private DescriptionInfo description;
private DescriptionInfo description = DescriptionInfo.empty();
@Nullable
private BiFunction<AnnotatedValueResolver, ResolverContext, Object> resolver;
@Nullable
Expand Down Expand Up @@ -1137,7 +1144,7 @@ private Builder typeElement(AnnotatedElement typeElement) {
/**
* Sets the description of the {@link AnnotatedElement}.
*/
private Builder description(@Nullable DescriptionInfo description) {
private Builder description(DescriptionInfo description) {
this.description = description;
return this;
}
Expand Down Expand Up @@ -1355,16 +1362,6 @@ private Class<?> getElementType(Type parameterizedType, boolean unwrapOptional)
return toRawType(elementType);
}

private static boolean isAnnotatedNullable(AnnotatedElement annotatedElement) {
for (Annotation a : annotatedElement.getAnnotations()) {
final String annotationTypeName = a.annotationType().getName();
if (annotationTypeName.endsWith(".Nullable")) {
return true;
}
}
return false;
}

@Nullable
private static ParameterizedType getParameterizedElementType(Type parameterizedType) {
if (!(parameterizedType instanceof ParameterizedType)) {
Expand Down
Loading

0 comments on commit 755c282

Please sign in to comment.