private static Map<String, Field> buildFields(Descriptor desc,
                                                  List<String> parentNames,
                                                  String namePrefix,
                                                  Set<Descriptor> visitedTypes,
                                                  HttpJsonTranscodingQueryParamMatchRule currentMatchRule,
                                                  Set<HttpJsonTranscodingQueryParamMatchRule> matchRules) {
        final ImmutableMap.Builder<String, Field> builder = ImmutableMap.builder();
        for (FieldDescriptor field : desc.getFields()) {
            final JavaType type = field.getJavaType();
            final boolean isRequired = hasRequiredFieldBehavior(field);
            final String fieldName;
            switch (currentMatchRule) {
                case ORIGINAL_FIELD:
                    fieldName = field.getName();
                    break;
                case LOWER_CAMEL_CASE:
                    fieldName = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, field.getName());
                    break;
                case JSON_NAME:
                    if (field.toProto().hasJsonName()) {
                        fieldName = field.toProto().getJsonName();
                    } else {
                        fieldName = null;
                    }
                    break;
                default:
                    throw new Error("Should never reach here");
            }
            if (fieldName == null) {
                // No matching name is found.
                continue;
            }

            final String key;
            if (namePrefix.isEmpty()) {
                key = fieldName;
            } else {
                key = namePrefix + '.' + fieldName;
            }
            switch (type) {
                case INT:
                case LONG:
                case FLOAT:
                case DOUBLE:
                case BOOLEAN:
                case STRING:
                case BYTE_STRING:
                case ENUM:
                    // Use field name which is specified in proto file.
                    builder.put(key, new Field(field, parentNames, field.getJavaType(), isRequired));
                    break;
                case MESSAGE:
                    @Nullable
                    final JavaType wellKnownFieldType = getJavaTypeForWellKnownTypes(field);

                    if (wellKnownFieldType != null) {
                        builder.put(key, new Field(field, parentNames, wellKnownFieldType, isRequired));
                        break;
                    }

                    if (visitedTypes.contains(field.getMessageType())) {
                        // Found recursion. No more analysis for this type.
                        // Raise an exception in order to mark the root parameter as JavaType.MESSAGE.
                        throw new RecursiveTypeException(field.getMessageType());
                    }

                    final Descriptor typeDesc = field.getMessageType();
                    // The json name should be used for the parent name because the keys are used to decode the
                    // JSON to the message by the Protobuf JSON decoder.
                    final String newParentName = field.getJsonName();
                    try {
                        for (HttpJsonTranscodingQueryParamMatchRule nestedMatchRule : matchRules) {
                            builder.putAll(buildFields(typeDesc,
                                                       ImmutableList.<String>builder()
                                                                    .addAll(parentNames)
                                                                    .add(newParentName)
                                                                    .build(),
                                                       key,
                                                       ImmutableSet.<Descriptor>builder()
                                                                   .addAll(visitedTypes)
                                                                   .add(field.getMessageType())
                                                                   .build(),
                                                       nestedMatchRule, matchRules));
                        }
                    } catch (RecursiveTypeException e) {
                        if (e.recursiveTypeDescriptor() != field.getMessageType()) {
                            // Re-throw the exception if it is not caused by my field.
                            throw e;
                        }

                        builder.put(key, new Field(field, parentNames, JavaType.MESSAGE, isRequired));
                    }
                    break;
            }
        }
        // A generated field in LOWER_CAMEL_CASE from a single word such as 'text' could be conflict with the
        // original field name.
        return builder.buildKeepingLast();
    }
Creates a new {@link GrpcService} instance from the given {@code delegate}. If it is possible
to support HTTP/JSON to gRPC transcoding, a new {@link HttpJsonTranscodingService} instance
would be returned. Otherwise, the {@code delegate} would be returned.
grpc/src/main/java/com/linecorp/armeria/server/grpc/HttpJsonTranscodingService.java