All Downloads are FREE. Search and download functionalities are using the official Maven repository.

guru.nidi.ramltester.core.ParameterChecker Maven / Gradle / Ivy

/*
 * Copyright (C) 2014 Stefan Niederhauser ([email protected])
 *
 * Licensed 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
 *
 *         http://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 guru.nidi.ramltester.core;

import guru.nidi.ramltester.model.Values;
import guru.nidi.ramltester.util.FileValue;
import guru.nidi.ramltester.util.Message;
import org.raml.model.ParamType;
import org.raml.model.parameter.AbstractParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 *
 */
class ParameterChecker {
    private static final Pattern INTEGER = Pattern.compile("0|-?[1-9][0-9]*");
    private static final Pattern NUMBER = Pattern.compile("0|inf|-inf|nan|-?(((0?|[1-9][0-9]*)\\.[0-9]*[1-9])|([1-9][0-9]*))(e[-+]?[1-9][0-9]*)?");
    private static final String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss 'GMT'";
    private static final String WILDCARD = "{?}";

    private final Logger log = LoggerFactory.getLogger(getClass());

    private final RamlViolations violations;
    private final boolean acceptUndefined;
    private final boolean acceptWildcard;
    private final boolean ignoreX;
    private final boolean caseSensitive;
    private final Set predefined;

    ParameterChecker(RamlViolations violations, boolean acceptUndefined, boolean acceptWildcard, boolean ignoreX, boolean caseSensitive, Set predefined) {
        this.violations = violations;
        this.acceptUndefined = acceptUndefined;
        this.acceptWildcard = acceptWildcard;
        this.ignoreX = ignoreX;
        this.caseSensitive = caseSensitive;
        this.predefined = predefined;
    }

    ParameterChecker(RamlViolations violations) {
        this(violations, false, false, false, true, Collections.emptySet());
    }

    ParameterChecker acceptUndefined() {
        return new ParameterChecker(violations, true, acceptWildcard, ignoreX, caseSensitive, predefined);
    }

    ParameterChecker acceptWildcard() {
        return new ParameterChecker(violations, acceptUndefined, true, ignoreX, caseSensitive, predefined);
    }

    ParameterChecker ignoreX(boolean ignoreX) {
        return new ParameterChecker(violations, acceptUndefined, acceptWildcard, ignoreX, caseSensitive, predefined);
    }

    ParameterChecker caseSensitive(boolean caseSensitive) {
        return new ParameterChecker(violations, acceptUndefined, acceptWildcard, ignoreX, caseSensitive, predefined);
    }

    ParameterChecker predefined(Set predefined) {
        return new ParameterChecker(violations, acceptUndefined, acceptWildcard, ignoreX, caseSensitive, predefined);
    }

    public Set checkParameters(Map params, Values values, Message message) {
        final Map> listParams = new HashMap<>();
        addToMapOfList(params, listParams);
        return checkListParameters(listParams, values, message);
    }

    private void addToMapOfList(Map params, Map> listParams) {
        for (final Map.Entry entry : params.entrySet()) {
            listParams.put(entry.getKey(), Collections.singletonList(entry.getValue()));
        }
    }

    private boolean acceptUndefined(String name) {
        return acceptUndefined || predefined.contains(name) || (ignoreX && name.startsWith("x-"));
    }

    public Set checkListParameters(Map> params, Values values, Message message) {
        final Set found = new HashSet<>();
        for (final Map.Entry> entry : values) {
            final Message namedMsg = message.withParam(entry.getKey());
            final String paramName = findMatchingParamName(params.keySet(), entry.getKey());
            final List parameters = params.get(paramName);
            if (parameters == null || parameters.isEmpty()) {
                violations.addIf(!acceptUndefined(entry.getKey().toLowerCase(Locale.ENGLISH)), namedMsg.withMessageParam("undefined"));
            } else {
                for (final AbstractParam parameter : parameters) {
                    violations.addIf(!parameter.isRepeat() && entry.getValue().size() > 1, namedMsg.withMessageParam("repeat.superfluous"));
                    for (final Object value : entry.getValue()) {
                        checkParameter(parameter, value, namedMsg);
                    }
                }
                found.add(paramName);
            }
        }
        for (final Map.Entry> entry : params.entrySet()) {
            final Message namedMsg = message.withParam(entry.getKey());
            for (final AbstractParam parameter : entry.getValue()) {
                violations.addIf(parameter.isRequired() && !found.contains(entry.getKey()), namedMsg.withMessageParam("required.missing"));
            }
        }
        return found;
    }

    private String findMatchingParamName(Collection paramNames, String name) {
        final String normalName = normalizeName(name);
        for (final String param : paramNames) {
            final String normalParam = normalizeName(param);
            final int pos = normalParam.indexOf(WILDCARD);
            if (normalParam.equals(normalName) || (acceptWildcard && pos >= 0 &&
                    nameMatchesKeyStart(normalName, normalParam, pos) && nameMatchesKeyEnd(normalName, normalParam, pos))) {
                return param;
            }
        }
        return null;
    }

    private String normalizeName(String name) {
        return caseSensitive ? name : name.toLowerCase(Locale.ENGLISH);
    }

    private boolean nameMatchesKeyStart(String name, String key, int wildcardPos) {
        return wildcardPos == 0 || name.startsWith(key.substring(0, wildcardPos));
    }

    private boolean nameMatchesKeyEnd(String name, String key, int wildcardPos) {
        return wildcardPos == key.length() - WILDCARD.length() ||
                name.endsWith(key.substring(wildcardPos + WILDCARD.length()));
    }

    public void checkParameter(AbstractParam param, Object value, Message message) {
        if (value == null) {
            final Message detail = message.withInnerParam(new Message("value", "empty"));
            checkNullParameter(param, detail);
        } else {
            final Message detail = message.withInnerParam(new Message("value", value));
            if (value instanceof String) {
                checkStringParameter(param, (String) value, detail);
            } else if (value instanceof FileValue) {
                checkFileParameter(param, detail);
            } else {
                throw new IllegalArgumentException("Unhandled parameter value '" + value + "' of type " + value.getClass());
            }
        }
    }

    private void checkNullParameter(AbstractParam param, Message detail) {
        if (param.getType() == ParamType.STRING) {
            checkStringParameter(param, "", detail);
        } else {
            violations.add(detail.withMessageParam("value.empty"));
        }
    }

    private void checkFileParameter(AbstractParam param, Message detail) {
        if (param.getType() != ParamType.FILE) {
            violations.add(detail.withMessageParam("file.superfluous", param.getType()));
        }
    }

    private void checkStringParameter(AbstractParam param, String value, Message detail) {
        switch (param.getType()) {
            case BOOLEAN:
                checkBoolean(value, detail);
                break;
            case DATE:
                checkDate(value, detail);
                break;
            case FILE:
                checkFile(detail);
                break;
            case INTEGER:
                checkInteger(param, value, detail);
                break;
            case NUMBER:
                checkNumber(param, value, detail);
                break;
            case STRING:
                checkString(param, value, detail);
                break;
            default:
                throw new RamlCheckerException("Unhandled parameter type '" + param.getType() + "'");
        }
    }

    private void checkString(AbstractParam param, String value, Message detail) {
        violations.addIf(param.getEnumeration() != null && !param.getEnumeration().contains(value),
                detail.withMessageParam("enum.invalid", param.getEnumeration()));
        try {
            violations.addIf(param.getPattern() != null && !JsRegex.matches(value, param.getPattern()),
                    detail.withMessageParam("pattern.invalid", param.getPattern()));
        } catch (PatternSyntaxException e) {
            log.warn("Could not execute regex '" + param.getPattern(), e);
        }
        violations.addIf(param.getMinLength() != null && value.length() < param.getMinLength(),
                detail.withMessageParam("length.tooSmall", param.getMinLength()));
        violations.addIf(param.getMaxLength() != null && value.length() > param.getMaxLength(),
                detail.withMessageParam("length.tooBig", param.getMaxLength()));
    }

    private void checkNumber(AbstractParam param, String value, Message detail) {
        if (NUMBER.matcher(value).matches()) {
            if ("inf".equals(value) || "-inf".equals(value) || "nan".equals(value)) {
                violations.addIf(param.getMinimum() != null || param.getMaximum() != null, detail.withMessageParam("unbound"));
            } else {
                checkNumericLimits(param, new BigDecimal(value), detail);
            }
        } else {
            violations.add(detail.withMessageParam("number.invalid"));
        }
    }

    private void checkInteger(AbstractParam param, String value, Message detail) {
        if (INTEGER.matcher(value).matches()) {
            checkNumericLimits(param, new BigDecimal(value), detail);
        } else {
            violations.add(detail.withMessageParam("integer.invalid"));
        }
    }

    private void checkFile(Message detail) {
        violations.add(detail.withMessageParam("file.invalid"));
    }

    private void checkDate(String value, Message detail) {
        try {
            final SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.ENGLISH);
            dateFormat.setLenient(false);
            dateFormat.parse(value);
        } catch (ParseException e) {
            violations.add(detail.withMessageParam("date.invalid"));
        }
    }

    private void checkBoolean(String value, Message detail) {
        violations.addIf(!"true".equals(value) && !"false".equals(value), detail.withMessageParam("boolean.invalid"));
    }

    private void checkNumericLimits(AbstractParam param, BigDecimal value, Message message) {
        violations.addIf(param.getMinimum() != null && param.getMinimum().compareTo(value) > 0,
                message.withMessageParam("value.tooSmall", param.getMinimum()));
        violations.addIf(param.getMaximum() != null && param.getMaximum().compareTo(value) < 0,
                message.withMessageParam("value.tooBig", param.getMaximum()));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy