Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,28 @@

package com.linecorp.armeria.internal.server.annotation;

import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.linecorp.armeria.internal.server.annotation.KotlinUtil.isKFunction;
import static com.linecorp.armeria.internal.server.annotation.KotlinUtil.isReturnTypeNothing;
import static com.linecorp.armeria.internal.server.annotation.KotlinUtil.kFunctionGenericReturnType;
import static com.linecorp.armeria.internal.server.annotation.KotlinUtil.kFunctionReturnType;
import static com.linecorp.armeria.server.docs.DocServiceTypeUtil.toTypeSignature;
import static com.linecorp.armeria.server.docs.FieldLocation.HEADER;
import static com.linecorp.armeria.server.docs.FieldLocation.PATH;
import static com.linecorp.armeria.server.docs.FieldLocation.QUERY;
import static com.linecorp.armeria.server.docs.FieldLocation.UNSPECIFIED;
import static java.util.Objects.requireNonNull;

import java.lang.annotation.Annotation;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
Expand Down Expand Up @@ -84,36 +75,11 @@
import com.linecorp.armeria.server.docs.TypeSignature;
import com.linecorp.armeria.server.docs.TypeSignatureType;

import io.netty.buffer.ByteBuf;

/**
* A {@link DocServicePlugin} implementation that supports the {@link AnnotatedService}.
*/
public final class AnnotatedDocServicePlugin implements DocServicePlugin {

@VisibleForTesting
static final TypeSignature VOID = TypeSignature.ofBase("void");
@VisibleForTesting
static final TypeSignature BOOLEAN = TypeSignature.ofBase("boolean");
@VisibleForTesting
static final TypeSignature BYTE = TypeSignature.ofBase("byte");
@VisibleForTesting
static final TypeSignature SHORT = TypeSignature.ofBase("short");
@VisibleForTesting
static final TypeSignature INT = TypeSignature.ofBase("int");
@VisibleForTesting
static final TypeSignature LONG = TypeSignature.ofBase("long");
@VisibleForTesting
static final TypeSignature FLOAT = TypeSignature.ofBase("float");
@VisibleForTesting
static final TypeSignature DOUBLE = TypeSignature.ofBase("double");
@VisibleForTesting
static final TypeSignature CHAR = TypeSignature.ofBase("char");
@VisibleForTesting
static final TypeSignature STRING = TypeSignature.ofBase("string");
@VisibleForTesting
static final TypeSignature BINARY = TypeSignature.ofBase("binary");

private static final ObjectWriter objectWriter = JacksonUtil.newDefaultObjectMapper()
.writerWithDefaultPrettyPrinter();

Expand Down Expand Up @@ -311,121 +277,6 @@ private static FieldInfo fieldInfo(AnnotatedValueResolver resolver) {
.build();
}

static TypeSignature toTypeSignature(Type type) {
requireNonNull(type, "type");

if (type instanceof JavaType) {
return toTypeSignature((JavaType) type);
}

// The data types defined by the OpenAPI Specification:

if (type == Void.class || type == void.class) {
return VOID;
}
if (type == Boolean.class || type == boolean.class) {
return BOOLEAN;
}
if (type == Byte.class || type == byte.class) {
return BYTE;
}
if (type == Short.class || type == short.class) {
return SHORT;
}
if (type == Integer.class || type == int.class) {
return INT;
}
if (type == Long.class || type == long.class) {
return LONG;
}
if (type == Float.class || type == float.class) {
return FLOAT;
}
if (type == Double.class || type == double.class) {
return DOUBLE;
}
if (type == Character.class || type == char.class) {
return CHAR;
}
if (type == String.class) {
return STRING;
}
if (type == byte[].class || type == Byte[].class ||
type == ByteBuffer.class || type == ByteBuf.class) {
return BINARY;
}
// End of data types defined by the OpenAPI Specification.

if (type instanceof ParameterizedType) {
final ParameterizedType parameterizedType = (ParameterizedType) type;
final Class<?> rawType = (Class<?>) parameterizedType.getRawType();
if (List.class.isAssignableFrom(rawType)) {
return TypeSignature.ofList(toTypeSignature(parameterizedType.getActualTypeArguments()[0]));
}
if (Set.class.isAssignableFrom(rawType)) {
return TypeSignature.ofSet(toTypeSignature(parameterizedType.getActualTypeArguments()[0]));
}

if (Map.class.isAssignableFrom(rawType)) {
final TypeSignature key = toTypeSignature(parameterizedType.getActualTypeArguments()[0]);
final TypeSignature value = toTypeSignature(parameterizedType.getActualTypeArguments()[1]);
return TypeSignature.ofMap(key, value);
}

if (Optional.class.isAssignableFrom(rawType) || "scala.Option".equals(rawType.getName())) {
return TypeSignature.ofOptional(toTypeSignature(parameterizedType.getActualTypeArguments()[0]));
}

final List<TypeSignature> actualTypes = Stream.of(parameterizedType.getActualTypeArguments())
.map(AnnotatedDocServicePlugin::toTypeSignature)
.collect(toImmutableList());
return TypeSignature.ofContainer(rawType.getSimpleName(), actualTypes);
}

if (type instanceof WildcardType) {
// Create an unresolved type with an empty string so that the type name will be '?'.
return TypeSignature.ofUnresolved("");
}
if (type instanceof TypeVariable) {
return TypeSignature.ofBase(type.getTypeName());
}
if (type instanceof GenericArrayType) {
return TypeSignature.ofList(toTypeSignature(((GenericArrayType) type).getGenericComponentType()));
}

if (!(type instanceof Class)) {
return TypeSignature.ofBase(type.getTypeName());
}

final Class<?> clazz = (Class<?>) type;
if (clazz.isArray()) {
// If it's an array, return it as a list.
return TypeSignature.ofList(toTypeSignature(clazz.getComponentType()));
}

return TypeSignature.ofStruct(clazz);
}

static TypeSignature toTypeSignature(JavaType type) {
if (type.isArrayType() || type.isCollectionLikeType()) {
return TypeSignature.ofList(toTypeSignature(type.getContentType()));
}

if (type.isMapLikeType()) {
final TypeSignature key = toTypeSignature(type.getKeyType());
final TypeSignature value = toTypeSignature(type.getContentType());
return TypeSignature.ofMap(key, value);
}

if (Optional.class.isAssignableFrom(type.getRawClass()) ||
"scala.Option".equals(type.getRawClass().getName())) {
return TypeSignature.ofOptional(
toTypeSignature(type.getBindings().getBoundType(0)));
}

return toTypeSignature(type.getRawClass());
}

private static FieldLocation location(AnnotatedValueResolver resolver) {
if (resolver.isPathVariable()) {
return PATH;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.linecorp.armeria.internal.server.annotation.AnnotatedDocServicePlugin.toTypeSignature;
import static com.linecorp.armeria.internal.server.annotation.AnnotatedValueResolver.isAnnotatedNullable;
import static com.linecorp.armeria.server.docs.DocServiceTypeUtil.toTypeSignature;
import static java.util.Objects.requireNonNull;

import java.lang.reflect.AnnotatedElement;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
package com.linecorp.armeria.internal.server.annotation;

import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.linecorp.armeria.internal.server.annotation.AnnotatedDocServicePlugin.toTypeSignature;
import static com.linecorp.armeria.internal.server.annotation.DefaultDescriptiveTypeInfoProvider.isNullable;
import static com.linecorp.armeria.server.docs.DocServiceTypeUtil.toTypeSignature;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.linecorp.armeria.server.docs;

import static java.util.Objects.requireNonNull;

import java.util.Map;
import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;

import com.linecorp.armeria.common.annotation.Nullable;
import com.linecorp.armeria.common.annotation.UnstableApi;

/**
* Metadata about a discriminator object, which is used for polymorphism.
* This corresponds to the {@code discriminator} object in the OpenAPI Specification.
* @see <a href="https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/">Inheritance and Polymorphism</a>
*/
@UnstableApi
public final class DiscriminatorInfo {

private final String propertyName;
private final Map<String, String> mapping;

/**
* Creates a new instance.
*
* @param propertyName the name of the property in the payload that will be used to differentiate
* between schemas.
* @param mapping a map of payload values to schema names or references.
*/
public DiscriminatorInfo(String propertyName, Map<String, String> mapping) {
this.propertyName = requireNonNull(propertyName, "propertyName");
this.mapping = ImmutableMap.copyOf(requireNonNull(mapping, "mapping"));
}

/**
* Returns the name of the property that is used to differentiate between schemas.
*/
@JsonProperty
public String propertyName() {
return propertyName;
}

/**
* Returns the map of payload values to schema names.
* The keys are the values that appear in the {@link #propertyName()} field, and the values are
* the schema definitions to use for that value (e.g., {@code "#/definitions/Cat"}).
*/
@JsonProperty
public Map<String, String> mapping() {
return mapping;
}

@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof DiscriminatorInfo)) {
return false;
}
final DiscriminatorInfo that = (DiscriminatorInfo) o;
return propertyName.equals(that.propertyName) && mapping.equals(that.mapping);
}

@Override
public int hashCode() {
return Objects.hash(propertyName, mapping);
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("propertyName", propertyName).add("mapping", mapping)
.toString();
}
}
Loading
Loading