Fix option type parsing

- In `CommandRegistration` add `ResolvableType` for `OptionSpec` giving
  more spesific handling of a type.
- In `CommandParser` handle source and target types so that we
  have generics with `List`, `Set` and arrays working better.
- In `HandlerMethodArgumentResolver` add better handling for
  `ConversionService` for generic types.
- In `StandardMethodTargetRegistrar` add better types via `ResolvableType`
  now that `CommandRegistration` support it.
- In `OptionConversionCommands` remove converter from `String` to `Set` as
  now things should work as is if generic in a `Set` has a converter.
- Backport #694 #699
- Fixes #700
This commit is contained in:
Janne Valkealahti
2023-04-05 18:56:09 +01:00
parent 64abc1da22
commit 2e19929a32
8 changed files with 358 additions and 24 deletions

View File

@@ -24,8 +24,10 @@ import jakarta.validation.Validator;
import org.jline.terminal.Terminal;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.support.MessageBuilder;
@@ -253,7 +255,13 @@ public interface CommandExecution {
@Override
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
return conversionService.convert(paramValues.get(parameter.getParameterName()), parameter.getParameterType());
Object source = paramValues.get(parameter.getParameterName());
if (source == null) {
return null;
}
TypeDescriptor sourceType = new TypeDescriptor(ResolvableType.forClass(source.getClass()), null, null);
TypeDescriptor targetType = new TypeDescriptor(parameter);
return conversionService.convert(source, sourceType, targetType);
}
}

View File

@@ -175,13 +175,27 @@ public interface CommandRegistration {
OptionSpec shortNames(Character... names);
/**
* Define a type for an option.
* Define a type for an option. This method is a shortcut for
* {@link #type(ResolvableType)} which is a preferred way to
* define type with generics. Will override one from
* {@link #type(ResolvableType)}.
*
* @param type the type
* @return option spec for chaining
* @see #type(ResolvableType)
*/
OptionSpec type(Type type);
/**
* Define a {@link ResolvableType} for an option. This method is
* a preferred way to define type with generics. Will override one
* from {@link #type(Type)}.
*
* @param type the resolvable type
* @return option spec for chaining
*/
OptionSpec type(ResolvableType type);
/**
* Define a {@code description} for an option.
*
@@ -817,6 +831,12 @@ public interface CommandRegistration {
return this;
}
@Override
public OptionSpec type(ResolvableType type) {
this.type = type;
return this;
}
@Override
public OptionSpec description(String description) {
this.description = description;

View File

@@ -27,6 +27,7 @@ import java.util.stream.Stream;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.shell.command.CommandOption;
import org.springframework.shell.command.CommandRegistration;
@@ -204,7 +205,8 @@ public interface Parser {
optionResults.add(OptionResult.of(o, null));
}
else {
Object value = convertOptionType(o, asdf);
Object toConvertValue = asdf.size() == 1 ? asdf.get(0) : asdf;
Object value = convertOptionType(o, toConvertValue);
optionResults.add(OptionResult.of(o, value));
}
@@ -362,8 +364,11 @@ public interface Parser {
return true;
}
if (conversionService != null && option.getType() != null && value != null) {
if (conversionService.canConvert(value.getClass(), option.getType().getRawClass())) {
value = conversionService.convert(value, option.getType().getRawClass());
Object source = value;
TypeDescriptor sourceType = new TypeDescriptor(ResolvableType.forClass(source.getClass()), null, null);
TypeDescriptor targetType = new TypeDescriptor(option.getType(), null, null);
if (conversionService.canConvert(sourceType, targetType)) {
value = conversionService.convert(source, sourceType, targetType);
}
}
return value;