Skip to content

Commit

Permalink
extend unit tests for placeholder functions
Browse files Browse the repository at this point in the history
  • Loading branch information
ffendt committed Feb 14, 2019
1 parent bb45d47 commit 4744840
Show file tree
Hide file tree
Showing 20 changed files with 257 additions and 136 deletions.
Expand Up @@ -112,7 +112,7 @@ private static boolean containsLegacyRequestSubjectIdPlaceholder(final CharSeque
* null, instead it should throw a specific exception if a placeholder cannot be replaced.
* @param unresolvedInputHandler exception handler providing a exception which is thrown when placeholders
* remain unresolved, e.g. when brackets have the wrong order.
* the replaced input, if the input contains placeholders; the (same) input object, if no placeholders were
* @return the replaced input, if the input contains placeholders; the (same) input object, if no placeholders were
* contained in the input.
* @throws IllegalStateException if {@code placeholderReplacerFunction} returns null
* @throws DittoRuntimeException the passed in {@code unresolvedInputHandler} will be used in order to throw the
Expand Down
Expand Up @@ -43,8 +43,7 @@ public static <I> EnforcementFilterFactory<I, String> newEnforcementFilterFactor
*/
public static <O> EnforcementFilterFactory<O, String> newEnforcementFilterFactory(final Enforcement enforcement,
final Placeholder<O> inputFilter) {
return new ImmutableEnforcementFilterFactory<>(enforcement, inputFilter,
PlaceholderFactory.newThingPlaceholder());
return newEnforcementFilterFactory(enforcement, inputFilter, PlaceholderFactory.newThingPlaceholder());
}

private EnforcementFactoryFactory() {
Expand Down
Expand Up @@ -32,7 +32,7 @@
public interface ExpressionResolver {

/**
* Resolves a complete expression template starting with a {@link Placeholder}s followed by optional pipeline stages
* Resolves a complete expression template starting with a {@link Placeholder} followed by optional pipeline stages
* (e.g. functions).
*
* @param expressionTemplate the expressionTemplate to resolve {@link Placeholder}s and and execute optional
Expand Down
Expand Up @@ -41,8 +41,7 @@ final class ImmutableExpressionResolver implements ExpressionResolver {

private final List<PlaceholderResolver<?>> placeholderResolvers;

ImmutableExpressionResolver(
final List<PlaceholderResolver<?>> placeholderResolvers) {
ImmutableExpressionResolver(final List<PlaceholderResolver<?>> placeholderResolvers) {
this.placeholderResolvers = Collections.unmodifiableList(new ArrayList<>(placeholderResolvers));
}

Expand All @@ -58,8 +57,9 @@ public String resolve(final String expressionTemplate, final boolean allowUnreso
makePlaceholderReplacerFunction(placeholderResolver);

placeholdersIdx++;
final boolean isLastPlaceholderResolver = placeholdersIdx == placeholderResolvers.size();
templateInWork = Placeholders.substitute(templateInWork, placeholderReplacerFunction,
UNRESOLVED_INPUT_HANDLER, placeholdersIdx < placeholderResolvers.size() || allowUnresolved);
UNRESOLVED_INPUT_HANDLER, !isLastPlaceholderResolver || allowUnresolved);
}

return templateInWork;
Expand Down Expand Up @@ -91,10 +91,10 @@ private Function<String, Optional<String>> makePlaceholderReplacerFunction(
final String placeholderTemplate =
pipelineStagesExpressions.get(0); // the first pipeline stage has to start with a placeholder

final List<String> pipelineStages =
pipelineStagesExpressions.subList(1, pipelineStagesExpressions.size()).stream()
.map(String::trim)
.collect(Collectors.toList());
final List<String> pipelineStages = pipelineStagesExpressions.stream()
.skip(1)
.map(String::trim)
.collect(Collectors.toList());
final Pipeline pipeline = new ImmutablePipeline(ImmutableFunctionExpression.INSTANCE, pipelineStages);

final Optional<String> pipelineInput = resolvePlaceholder(placeholderResolver, placeholderTemplate);
Expand Down Expand Up @@ -141,4 +141,5 @@ public String toString() {
"placeholderResolvers=" + placeholderResolvers +
"]";
}

}
Expand Up @@ -10,7 +10,8 @@
*/
package org.eclipse.ditto.model.placeholders;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
Expand All @@ -28,16 +29,13 @@ final class ImmutableFunctionExpression implements FunctionExpression {
*/
static final ImmutableFunctionExpression INSTANCE = new ImmutableFunctionExpression();

private static final List<PipelineFunction> SUPPORTED;

static {
SUPPORTED = new ArrayList<>();
SUPPORTED.add(new PipelineFunctionDefault()); // fn:default('fallback value')
SUPPORTED.add(new PipelineFunctionSubstringBefore()); // fn:substring-before(':')
SUPPORTED.add(new PipelineFunctionSubstringAfter()); // fn:substring-after(':')
SUPPORTED.add(new PipelineFunctionLower()); // fn:lower()
SUPPORTED.add(new PipelineFunctionUpper()); // fn:upper()
}
private static final List<PipelineFunction> SUPPORTED = Collections.unmodifiableList(Arrays.asList(
new PipelineFunctionDefault(), // fn:default('fallback value')
new PipelineFunctionSubstringBefore(), // fn:substring-before(':')
new PipelineFunctionSubstringAfter(), // fn:substring-after(':')
new PipelineFunctionLower(), // fn:lower()
new PipelineFunctionUpper() // fn:upper()
));

@Override
public String getPrefix() {
Expand All @@ -55,8 +53,8 @@ public List<String> getSupportedNames() {
@Override
public boolean supports(final String expression) {

// it is sufficient that the passed in name starts with the function name, e.g.: default('foo')
// the function validates itself whether the remaining part is valid
// it is sufficient that the passed in name starts with the function name and an opening parentheses,
// e.g.: default('foo'). the function validates itself whether the remaining part is valid.
return SUPPORTED.stream()
.map(PipelineFunction::getName)
.map(psfName -> psfName.replaceFirst(getPrefix() + ":", ""))
Expand All @@ -73,7 +71,8 @@ public Optional<String> resolve(final String expression, final Optional<String>

return SUPPORTED.stream()
.filter(pf -> expression.startsWith(getPrefix() + ":" + pf.getName() + "("))
.map(pf -> pf.apply(resolvedInputValue, expression.replaceFirst(getPrefix() + ":" + pf.getName(), "").trim(),
.map(pf -> pf.apply(resolvedInputValue,
expression.replaceFirst(getPrefix() + ":" + pf.getName(), "").trim(),
expressionResolver)
)
.findFirst()
Expand Down
Expand Up @@ -47,19 +47,18 @@ public DefaultFunctionSignature getSignature() {
public Optional<String> apply(final Optional<String> value, final String paramsIncludingParentheses,
final ExpressionResolver expressionResolver) {

// parse + resolve the specified default value:
final ResolvedFunctionParameter<String> resolvedDefaultParam =
parseAndResolve(paramsIncludingParentheses, expressionResolver).get(0);

if (value.isPresent()) {
// if previous stage was non-empty: proceed with that
return value;
} else {
// parse + resolve the specified default value:
final ResolvedFunctionParameter<String> resolvedDefaultParam =
parseAndResolve(paramsIncludingParentheses, expressionResolver);
return Optional.of(resolvedDefaultParam.getValue());
}
}

private List<ResolvedFunctionParameter> parseAndResolve(final String paramsIncludingParentheses,
private ResolvedFunctionParameter<String> parseAndResolve(final String paramsIncludingParentheses,
final ExpressionResolver expressionResolver) {

final ParameterDefinition<String> defaultValueParam = getSignature().getParameterDefinition(0);
Expand All @@ -69,15 +68,13 @@ private List<ResolvedFunctionParameter> parseAndResolve(final String paramsInclu
String constant = matcher.group("singleQuotedConstant");
constant = constant != null ? constant : matcher.group("doubleQuotedConstant");
if (constant != null) {
return Collections.singletonList(
new ResolvedDefaultValueParam(defaultValueParam, constant));
return new ResolvedDefaultValueParam(defaultValueParam, constant);
}

final String placeholder = matcher.group("placeholder");
if (placeholder != null) {
final Optional<String> resolved = expressionResolver.resolveSinglePlaceholder(placeholder);
return Collections.singletonList(
new ResolvedDefaultValueParam(defaultValueParam, resolved.orElse(placeholder)));
return new ResolvedDefaultValueParam(defaultValueParam, resolved.orElse(placeholder));
}
}

Expand Down
Expand Up @@ -44,20 +44,19 @@ public Optional<String> apply(final Optional<String> value, final String paramsI
final ExpressionResolver expressionResolver) {

// check if signature matches (empty params!)
parseAndResolve(paramsIncludingParentheses);
validateOrThrow(paramsIncludingParentheses);
return value.map(String::toLowerCase);
}

private List<ResolvedFunctionParameter> parseAndResolve(final String paramsIncludingParentheses) {
private void validateOrThrow(final String paramsIncludingParentheses) {

final Matcher matcher = OVERALL_PATTERN.matcher(paramsIncludingParentheses);
if (matcher.matches()) {
if (!matcher.matches()) {

return Collections.emptyList();
throw PlaceholderFunctionSignatureInvalidException.newBuilder(paramsIncludingParentheses, this)
.build();
}

throw PlaceholderFunctionSignatureInvalidException.newBuilder(paramsIncludingParentheses, this)
.build();
}

/**
Expand Down
Expand Up @@ -45,8 +45,7 @@ public Signature getSignature() {
public Optional<String> apply(final Optional<String> value, final String paramsIncludingParentheses,
final ExpressionResolver expressionResolver) {

final ResolvedFunctionParameter<String> resolvedSubstringBeforeParam =
parseAndResolve(paramsIncludingParentheses).get(0);
final ResolvedFunctionParameter<String> resolvedSubstringBeforeParam = parseAndResolve(paramsIncludingParentheses);
final String splitValue = resolvedSubstringBeforeParam.getValue();

return value.map(previousStage -> {
Expand All @@ -58,7 +57,7 @@ public Optional<String> apply(final Optional<String> value, final String paramsI
});
}

private List<ResolvedFunctionParameter> parseAndResolve(final String paramsIncludingParentheses) {
private ResolvedFunctionParameter<String> parseAndResolve(final String paramsIncludingParentheses) {

final ParameterDefinition<String> givenStringParam = getSignature().getParameterDefinition(0);
final Matcher matcher = OVERALL_PATTERN.matcher(paramsIncludingParentheses);
Expand All @@ -67,7 +66,7 @@ private List<ResolvedFunctionParameter> parseAndResolve(final String paramsInclu
String constant = matcher.group("singleQuotedConstant");
constant = constant != null ? constant : matcher.group("doubleQuotedConstant");
if (constant != null) {
return Collections.singletonList(new ResolvedGivenStringParam(givenStringParam, constant));
return new ResolvedGivenStringParam(givenStringParam, constant);
}
}

Expand Down
Expand Up @@ -45,8 +45,7 @@ public Signature getSignature() {
public Optional<String> apply(final Optional<String> value, final String paramsIncludingParentheses,
final ExpressionResolver expressionResolver) {

final ResolvedFunctionParameter<String> resolvedSubstringBeforeParam =
parseAndResolve(paramsIncludingParentheses).get(0);
final ResolvedFunctionParameter<String> resolvedSubstringBeforeParam = parseAndResolve(paramsIncludingParentheses);
final String splitValue = resolvedSubstringBeforeParam.getValue();

return value.map(previousStage -> {
Expand All @@ -58,7 +57,7 @@ public Optional<String> apply(final Optional<String> value, final String paramsI
});
}

private List<ResolvedFunctionParameter> parseAndResolve(final String paramsIncludingParentheses) {
private ResolvedFunctionParameter<String> parseAndResolve(final String paramsIncludingParentheses) {

final ParameterDefinition<String> givenStringParam = getSignature().getParameterDefinition(0);
final Matcher matcher = OVERALL_PATTERN.matcher(paramsIncludingParentheses);
Expand All @@ -67,7 +66,7 @@ private List<ResolvedFunctionParameter> parseAndResolve(final String paramsInclu
String constant = matcher.group("singleQuotedConstant");
constant = constant != null ? constant : matcher.group("doubleQuotedConstant");
if (constant != null) {
return Collections.singletonList(new ResolvedGivenStringParam(givenStringParam, constant));
return new ResolvedGivenStringParam(givenStringParam, constant);
}
}

Expand Down
Expand Up @@ -44,20 +44,17 @@ public Optional<String> apply(final Optional<String> value, final String paramsI
final ExpressionResolver expressionResolver) {

// check if signature matches (empty params!)
parseAndResolve(paramsIncludingParentheses);
validateOrThrow(paramsIncludingParentheses);
return value.map(String::toUpperCase);
}

private List<ResolvedFunctionParameter> parseAndResolve(final String paramsIncludingParentheses) {
private void validateOrThrow(final String paramsIncludingParentheses) {

final Matcher matcher = OVERALL_PATTERN.matcher(paramsIncludingParentheses);
if (matcher.matches()) {

return Collections.emptyList();
if (!matcher.matches()) {
throw PlaceholderFunctionSignatureInvalidException.newBuilder(paramsIncludingParentheses, this)
.build();
}

throw PlaceholderFunctionSignatureInvalidException.newBuilder(paramsIncludingParentheses, this)
.build();
}

/**
Expand Down
Expand Up @@ -22,7 +22,7 @@ public interface Placeholder<T> extends Expression {
/**
* Resolves the placeholder variable by name.
*
* @param placeholderSource the source from which to the placeholder is resolved
* @param placeholderSource the source from which to the placeholder is resolved, e.g. a Thing id.
* @param name the placeholder variable name (i. e., the part after ':').
* @return value of the placeholder variable if the placeholder name is supported, or an empty optional otherwise.
*/
Expand Down
Expand Up @@ -160,35 +160,30 @@ public static String apply(final String template, final ExpressionResolver expre
}

/**
* Validates that the passed {@code template} is both valid and depending on the {@code allowUnresolved} boolean
* that the placeholders in the passed {@code template} are completely replaceable by the provided
* {@code placeholders}.
* Validates that the passed {@code template} is valid and that the placeholders in the passed {@code template}
* are completely replaceable by the provided {@code placeholders}.
*
* @param template a string potentially containing placeholders to replace
* @param allowUnresolved whether to allow if there could be placeholders in the template left unreplaced
* @param placeholders the {@link Placeholder}s to use for replacement
* @throws UnresolvedPlaceholderException in case the template's placeholders could not completely be resolved
*/
public static void validate(final String template, final boolean allowUnresolved,
final Placeholder<?>... placeholders) {
public static void validate(final String template, final Placeholder<?>... placeholders) {
String replaced = template;
for (int i = 0; i < placeholders.length; i++) {
boolean isNotLastPlaceholder = i < placeholders.length - 1;
final Placeholder thePlaceholder = placeholders[i];
final ExpressionResolver expressionResolver = PlaceholderFactory
.newExpressionResolverForValidation(thePlaceholder);

replaced = doApply(replaced, expressionResolver, allowUnresolved || isNotLastPlaceholder);
replaced = doApply(replaced, expressionResolver, isNotLastPlaceholder);
}
}

private static String doApply(final String template,
final ExpressionResolver expressionResolver,
final boolean allowUnresolved) {

String templateInWork = template;
templateInWork = expressionResolver.resolve(templateInWork, allowUnresolved);
return templateInWork;
return expressionResolver.resolve(template, allowUnresolved);
}

static String checkAllPlaceholdersResolved(final String input) {
Expand Down

0 comments on commit 4744840

Please sign in to comment.