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
10 changes: 8 additions & 2 deletions src/main/java/com/hubspot/jinjava/el/TruthyTypeConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,16 @@ protected Byte coerceToByte(Object value) {

@Override
protected String coerceToString(Object value) {
if (value instanceof DummyObject) {
if (value instanceof DummyObject || value == null) {
return "";
}
return super.coerceToString(value);

// super() behavior breaks equality between enums and strings so removed here.
if (value instanceof String) {
return (String) value;
}

return value.toString();
}

@Override
Expand Down
74 changes: 40 additions & 34 deletions src/main/java/com/hubspot/jinjava/lib/filter/AbstractFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.google.common.collect.ImmutableMap;
import com.hubspot.jinjava.doc.annotations.JinjavaDoc;
import com.hubspot.jinjava.doc.annotations.JinjavaParam;
import com.hubspot.jinjava.el.TruthyTypeConverter;
import com.hubspot.jinjava.interpret.InvalidInputException;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import java.math.BigDecimal;
Expand All @@ -27,12 +28,9 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;

/***
* Filter base that uses Filter Jinjavadoc to construct named argument parameters.
Expand All @@ -41,9 +39,10 @@
* @see JinjavaDoc
* @see JinjavaParam
*/
public abstract class AbstractFilter implements Filter {
public abstract class AbstractFilter implements Filter, AdvancedFilter {
private static final Map<Class, Map<String, JinjavaParam>> NAMED_ARGUMENTS_CACHE = new ConcurrentHashMap<>();
private static final Map<Class, Map<String, Object>> DEFAULT_VALUES_CACHE = new ConcurrentHashMap<>();
protected static final TruthyTypeConverter TYPE_CONVERTER = new TruthyTypeConverter();

private final Map<String, JinjavaParam> namedArguments;
private final Map<String, Object> defaultValues;
Expand Down Expand Up @@ -124,46 +123,40 @@ protected Object parseArg(
JinjavaParam jinjavaParamMetadata,
Object value
) {
if (
jinjavaParamMetadata.type() == null ||
value == null ||
Arrays.asList("object", "dict", "sequence").contains(jinjavaParamMetadata.type())
) {
if (jinjavaParamMetadata.type() == null || value == null) {
return value;
}
String valueString = Objects.toString(value, null);
switch (jinjavaParamMetadata.type().toLowerCase()) {
case "boolean":
return value instanceof Boolean
? (Boolean) value
: BooleanUtils.toBooleanObject(valueString);
return TYPE_CONVERTER.convert(value, Boolean.class);
case "int":
return value instanceof Integer
? (Integer) value
: NumberUtils.toInt(valueString);
return TYPE_CONVERTER.convert(value, Integer.class);
case "long":
return value instanceof Long ? (Long) value : NumberUtils.toLong(valueString);
return TYPE_CONVERTER.convert(value, Long.class);
case "float":
return value instanceof Float ? (Float) value : NumberUtils.toFloat(valueString);
return TYPE_CONVERTER.convert(value, Float.class);
case "double":
return value instanceof Double
? (Double) value
: NumberUtils.toDouble(valueString);
return TYPE_CONVERTER.convert(value, Double.class);
case "number":
return value instanceof Number ? (Number) value : new BigDecimal(valueString);
return TYPE_CONVERTER.convert(value, BigDecimal.class);
case "string":
return valueString;
return TYPE_CONVERTER.convert(value, String.class);
case "object":
case "dict":
case "sequence":
return value;
default:
throw new InvalidInputException(
interpreter,
"INVALID_ARG_NAME",
String.format(
"Argument named '%s' with value '%s' cannot be parsed for filter %s",
jinjavaParamMetadata.value(),
value,
getName()
)
String errorMessage = String.format(
"Argument named '%s' with value '%s' cannot be parsed for filter '%s'",
jinjavaParamMetadata.value(),
value,
getName()
);
if (interpreter != null) { //Filter runtime vs init
throw new InvalidInputException(interpreter, "INVALID_ARG_NAME", errorMessage);
} else {
throw new IllegalArgumentException(errorMessage);
}
}
}

Expand All @@ -172,7 +165,13 @@ public void validateArgs(
Map<String, Object> parsedArgs
) {
for (JinjavaParam jinjavaParam : namedArguments.values()) {
if (jinjavaParam.required() && !parsedArgs.containsKey(jinjavaParam.value())) {
if (
jinjavaParam.required() &&
(
!parsedArgs.containsKey(jinjavaParam.value()) ||
parsedArgs.get(jinjavaParam.value()) == null
)
) {
throw new InvalidInputException(
interpreter,
"MISSING_REQUIRED_ARG",
Expand Down Expand Up @@ -207,6 +206,10 @@ public String getIndexedArgumentName(int position) {
.orElse(null);
}

public Object getDefaultValue(String argName) {
return defaultValues.get(argName);
}

public Map<String, JinjavaParam> initNamedArguments() {
JinjavaDoc jinjavaDoc = this.getClass().getAnnotation(JinjavaDoc.class);

Expand Down Expand Up @@ -242,7 +245,10 @@ public Map<String, Object> initDefaultValues() {
.stream()
.filter(e -> StringUtils.isNotEmpty(e.getValue().defaultValue()))
.collect(
ImmutableMap.toImmutableMap(Map.Entry::getKey, e -> e.getValue().defaultValue())
ImmutableMap.toImmutableMap(
Map.Entry::getKey,
e -> parseArg(null, e.getValue(), e.getValue().defaultValue())
)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import com.hubspot.jinjava.lib.fn.Functions;
import com.hubspot.jinjava.objects.date.StrftimeFormatter;
import java.util.Map;

@JinjavaDoc(
value = "Formats a date object",
Expand All @@ -17,19 +18,19 @@
),
params = {
@JinjavaParam(
value = "format",
value = DateTimeFormatFilter.FORMAT_PARAM,
defaultValue = StrftimeFormatter.DEFAULT_DATE_FORMAT,
desc = "The format of the date determined by the directives added to this parameter"
),
@JinjavaParam(
value = "timezone",
defaultValue = "utc",
value = DateTimeFormatFilter.TIMEZONE_PARAM,
defaultValue = "UTC",
desc = "Time zone of output date"
),
@JinjavaParam(
value = "locale",
value = DateTimeFormatFilter.LOCALE_PARAM,
type = "string",
defaultValue = "us",
defaultValue = "en-US",
desc = "The language code to use when formatting the datetime"
)
},
Expand All @@ -40,19 +41,29 @@
)
}
)
public class DateTimeFormatFilter implements Filter {
public class DateTimeFormatFilter extends AbstractFilter implements Filter {
public static final String FORMAT_PARAM = "format";
public static final String TIMEZONE_PARAM = "timezone";
public static final String LOCALE_PARAM = "locale";

@Override
public String getName() {
return "datetimeformat";
}

@Override
public Object filter(Object var, JinjavaInterpreter interpreter, String... args) {
if (args.length > 0) {
return Functions.dateTimeFormat(var, args);
} else {
public Object filter(
Object var,
JinjavaInterpreter interpreter,
Map<String, Object> parsedArgs
) {
String format = (String) parsedArgs.get(FORMAT_PARAM);
String timezone = (String) parsedArgs.get(TIMEZONE_PARAM);
String locale = (String) parsedArgs.get(LOCALE_PARAM);
if (format == null && timezone == null && locale == null) {
return Functions.dateTimeFormat(var);
} else {
return Functions.dateTimeFormat(var, format, timezone, locale);
}
}
}
25 changes: 13 additions & 12 deletions src/main/java/com/hubspot/jinjava/lib/filter/JoinFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,20 @@
import com.hubspot.jinjava.util.ForLoop;
import com.hubspot.jinjava.util.LengthLimitingStringBuilder;
import com.hubspot.jinjava.util.ObjectIterator;
import java.util.Map;
import java.util.Objects;

@JinjavaDoc(
value = "Return a string which is the concatenation of the strings in the sequence.",
input = @JinjavaParam(value = "value", desc = "The values to join", required = true),
params = {
@JinjavaParam(
value = "d",
value = JoinFilter.SEPARATOR_PARAM,
desc = "The separator string used to join the items",
defaultValue = "(empty String)"
),
@JinjavaParam(
value = "attr",
value = JoinFilter.ATTRIBUTE_PARAM,
desc = "Optional dict object attribute to use in joining"
)
},
Expand All @@ -37,28 +38,28 @@
)
}
)
public class JoinFilter implements Filter {
public class JoinFilter extends AbstractFilter implements Filter {
public static final String SEPARATOR_PARAM = "d";
public static final String ATTRIBUTE_PARAM = "attribute";

@Override
public String getName() {
return "join";
}

@Override
public Object filter(Object var, JinjavaInterpreter interpreter, String... args) {
public Object filter(
Object var,
JinjavaInterpreter interpreter,
Map<String, Object> parsedArgs
) {
LengthLimitingStringBuilder stringBuilder = new LengthLimitingStringBuilder(
interpreter.getConfig().getMaxStringLength()
);

String separator = "";
if (args.length > 0) {
separator = args[0];
}
String separator = (String) parsedArgs.get(SEPARATOR_PARAM);

String attr = null;
if (args.length > 1) {
attr = args[1];
}
String attr = (String) parsedArgs.get(ATTRIBUTE_PARAM);

ForLoop loop = ObjectIterator.getLoop(var);
boolean first = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import com.hubspot.jinjava.interpret.InvalidInputException;
import com.hubspot.jinjava.interpret.InvalidReason;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import com.hubspot.jinjava.interpret.TemplateSyntaxException;
import java.util.Map;

@JinjavaDoc(
value = "Return a copy of the value with all occurrences of a matched regular expression (Java RE2 syntax) " +
Expand All @@ -23,12 +23,12 @@
),
params = {
@JinjavaParam(
value = "regex",
value = RegexReplaceFilter.REGEX_KEY,
desc = "The regular expression that you want to match and replace",
required = true
),
@JinjavaParam(
value = "new",
value = RegexReplaceFilter.REPLACE_WITH,
desc = "The new string that you replace the matched substring",
required = true
)
Expand All @@ -40,39 +40,29 @@
)
}
)
public class RegexReplaceFilter implements Filter {
public class RegexReplaceFilter extends AbstractFilter {
public static final String REGEX_KEY = "regex";
public static final String REPLACE_WITH = "new";

@Override
public String getName() {
return "regex_replace";
}

@Override
public Object filter(Object var, JinjavaInterpreter interpreter, String... args) {
if (args.length < 2) {
throw new TemplateSyntaxException(
interpreter,
getName(),
"requires 2 arguments (regex string, replacement string)"
);
}

if (args[0] == null || args[1] == null) {
throw new TemplateSyntaxException(
interpreter,
getName(),
"requires both a valid regex and new params (not null)"
);
}

public Object filter(
Object var,
JinjavaInterpreter interpreter,
Map<String, Object> parsedArgs
) {
if (var == null) {
return null;
}

if (var instanceof String) {
String s = (String) var;
String toReplace = args[0];
String replaceWith = args[1];
String toReplace = (String) parsedArgs.get(REGEX_KEY);
String replaceWith = (String) parsedArgs.get(REPLACE_WITH);

try {
Pattern p = Pattern.compile(toReplace);
Expand Down
Loading