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

com.reprezen.kaizen.oasparser.val.ValidatorBase Maven / Gradle / Ivy

package com.reprezen.kaizen.oasparser.val;

import static com.reprezen.kaizen.oasparser.val.BaseValidationMessages.BadEmail;
import static com.reprezen.kaizen.oasparser.val.BaseValidationMessages.BadPattern;
import static com.reprezen.kaizen.oasparser.val.BaseValidationMessages.BadUrl;
import static com.reprezen.kaizen.oasparser.val.BaseValidationMessages.DuplicateValue;
import static com.reprezen.kaizen.oasparser.val.BaseValidationMessages.EmptyList;
import static com.reprezen.kaizen.oasparser.val.BaseValidationMessages.MissingField;
import static com.reprezen.kaizen.oasparser.val.BaseValidationMessages.NumberConstraint;
import static com.reprezen.kaizen.oasparser.val.BaseValidationMessages.PatternMatchFail;
import static com.reprezen.kaizen.oasparser.val.BaseValidationMessages.WrongTypeJson;
import static com.reprezen.kaizen.oasparser.val.msg.Messages.msg;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;

import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.BigIntegerNode;
import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.IntNode;
import com.fasterxml.jackson.databind.node.NumericNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ShortNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.reprezen.jsonoverlay.BooleanOverlay;
import com.reprezen.jsonoverlay.IntegerOverlay;
import com.reprezen.jsonoverlay.JsonOverlay;
import com.reprezen.jsonoverlay.ListOverlay;
import com.reprezen.jsonoverlay.MapOverlay;
import com.reprezen.jsonoverlay.NumberOverlay;
import com.reprezen.jsonoverlay.ObjectOverlay;
import com.reprezen.jsonoverlay.Overlay;
import com.reprezen.jsonoverlay.PrimitiveOverlay;
import com.reprezen.jsonoverlay.PropertiesOverlay;
import com.reprezen.jsonoverlay.StringOverlay;

public abstract class ValidatorBase implements Validator {
	protected Overlay value;
	protected ValidationResults results;

	@Override
	public final void validate(Overlay value) {
		this.value = value;
		this.results = ValidationContext.getValidationResults();
		checkJsonType(value, getAllowedJsonTypes(value), results);
		runValidations();
	}

	public abstract void runValidations();

	public Overlay validateBooleanField(String name, boolean required) {
		return validateField(name, required, Boolean.class, null);
	}

	public Overlay validateStringField(String name, boolean required) {
		return validateStringField(name, required, (Pattern) null);
	}

	public Overlay validateStringField(String name, boolean required, String pattern) {
		return validateStringField(name, required, Pattern.compile(pattern));
	}

	@SafeVarargs
	public final Overlay validateStringField(String name, boolean required, Pattern pattern,
			Consumer>... otherChecks) {
		Overlay field = validateField(name, required, String.class, null);
		checkMissing(field, required);
		if (field != null && field.isPresent()) {
			if (pattern != null) {
				checkPattern(field, pattern);
			}
			for (Consumer> otherCheck : otherChecks) {
				otherCheck.accept(field);
			}
		}
		return field;
	}

	void checkPattern(Overlay field, Pattern pattern) {
		if (!pattern.matcher(field.get()).matches()) {
			results.addError(msg(PatternMatchFail, field.get(), pattern), field);
		}
	}

	public Overlay validatePatternField(String name, boolean required) {
		return validateStringField(name, required, null, this::checkRegex);
	}

	private void checkRegex(Overlay field) {
		String regex = field.get();
		try {
			Pattern.compile(regex);
		} catch (PatternSyntaxException e) {
			results.addWarning(msg(BadPattern, regex), field);
		}
	}

	public Overlay validateUrlField(String name, boolean required, boolean allowRelative, boolean allowVars) {
		return validateUrlField(name, required, allowRelative, allowVars, (Pattern) null);
	}

	public Overlay validateUrlField(String name, boolean required, boolean allowRelative, boolean allowVars,
			String pattern) {
		return validateUrlField(name, required, allowRelative, allowVars, Pattern.compile(pattern));
	}

	public Overlay validateUrlField(String name, boolean required, boolean allowRelative, boolean allowVars,
			Pattern pattern) {
		return validateStringField(name, required, pattern, field -> checkUrl(field, allowRelative, allowVars));
	}

	private static String FAKE_SCHEME = "oasparser.fake.scheme";
	private static URLStreamHandler fakeHandler = new URLStreamHandler() {
		@Override
		protected URLConnection openConnection(URL u) throws IOException {
			return null;
		}
	};

	private void checkUrl(Overlay overlay, boolean allowRelative, boolean allowVars) {
		// TODO Q: Any help from spec in being able to validate URLs with vars? E.g is
		// our treatment here valid? We assume vars can only appear where simple text
		// can appear, so handling vars means relacing {.*} with "1" and testing for URL
		// validity. We use a digit instead of a letter because it covers vars in the
		// port, and elsewhere digits are always allowed where letters are.
		String origUrl = overlay.get();
		String url = origUrl;
		boolean fake = false;
		if (allowVars) {
			url = url.replaceAll("\\{[^}]+\\}", "1");
			if (url.startsWith("1:")) {
				// "1" is not a valid scheme name, so we need to replace it with special scheme,
				// for which we provide a do-nothing protocol handler implementation
				url = FAKE_SCHEME + url.substring(1);
				fake = true;
			}
		}
		try {
			new URL(null, url, fake ? fakeHandler : null);
		} catch (MalformedURLException e) {
			try {
				URL context = new URL(null, FAKE_SCHEME + ":/", fakeHandler);
				new URL(context, url);
				if (!allowRelative) {
					results.addError(msg(BaseValidationMessages.NoRelUrl, origUrl, e.toString()), overlay);
				}
			} catch (MalformedURLException e1) {
				results.addError(msg(BadUrl, origUrl, e.toString()), overlay);
			}
		}
	}

	public Overlay validateEmailField(String name, boolean required) {
		return validateEmailField(name, required, (Pattern) null);
	}

	public Overlay validateUrlField(String name, boolean required, String pattern) {
		return validateEmailField(name, required, Pattern.compile(pattern));
	}

	public Overlay validateEmailField(String name, boolean required, Pattern pattern) {
		return validateStringField(name, required, pattern, this::checkEmail);
	}

	private void checkEmail(Overlay overlay) {
		String email = overlay.get();
		try {
			InternetAddress addr = new InternetAddress();
			addr.setAddress(email);
			addr.validate();
		} catch (AddressException e) {
			results.addError(msg(BadEmail, email, e.toString()), overlay);
		}
	}

	public Overlay validatePositiveField(String name, boolean required) {
		return validateNumericField(name, required, NumericUtils::isPositive, "be positive");
	}

	public Overlay validateNonNegativeField(String name, boolean required) {
		return validateNumericField(name, required, NumericUtils::isNonNegative, "not be positive");
	}

	public Overlay validateNumericField(String name, boolean required, Function test,
			String desc) {
		Overlay field = validateField(name, required, Number.class, null);
		checkMissing(field, required);
		if (field != null && field.isPresent() && test != null) {
			Number n = field.get();
			if (!test.apply(n)) {
				results.addError(msg(NumberConstraint, desc, n), field);
			}
		}
		return field;
	}

	@SafeVarargs
	public final  Overlay validateField(String name, boolean required, Class fieldClass,
			Validator validator, Consumer>... otherChecks) {
		@SuppressWarnings("unchecked")
		PropertiesOverlay propValue = (PropertiesOverlay) value.get();
		Overlay field = Overlay.of(propValue, name, fieldClass);
		checkJsonType(field, getAllowedJsonTypes(field), results);
		checkMissing(field, required);
		if (field != null && field.isPresent() && validator != null) {
			validator.validate(field);
			for (Consumer> otherCheck : otherChecks) {
				otherCheck.accept(field);
			}
		}
		return field;
	}

	public  Overlay> validateListField(String name, boolean nonEmpty, boolean unique, Class itemClass,
			Validator itemValidator) {
		@SuppressWarnings("unchecked")
		Overlay> list = (Overlay>) (Object) Overlay.of((PropertiesOverlay) value.get(), name,
				List.class);
		validateList(list, nonEmpty, unique, itemValidator);
		return list;
	}

	private  void validateList(Overlay> list, boolean nonEmpty, boolean unique, Validator itemValidator) {
		new ListValidator(itemValidator).validate(list);
		checkListNotEmpty(list, nonEmpty);
		checkListUnique(list, unique);
	}

	private  void checkListNotEmpty(Overlay> list, boolean nonEmpty) {
		if (nonEmpty) {
			ListOverlay listOverlay = Overlay.getListOverlay(list);
			if (list != null && !list.isPresent()) {
				if (nonEmpty && listOverlay.size() == 0) {
					results.addError(msg(EmptyList), list);
				}
			}
		}
	}

	private  void checkListUnique(Overlay> list, boolean unique) {
		if (unique) {
			ListOverlay listOverlay = Overlay.getListOverlay(list);
			Set seen = new HashSet<>();
			for (int i = 0; i < listOverlay.size(); i++) {
				X item = listOverlay.get(i);
				if (seen.contains(item)) {
					results.addError(msg(DuplicateValue, item, i), Overlay.of(listOverlay, i));
				} else {
					seen.add(item);
				}
			}
		}
	}

	public  Overlay> validateMapField(String name, boolean nonEmpty, boolean unique,
			Class valueClass, Validator valueValidator) {
		@SuppressWarnings("unchecked")
		Overlay> map = (Overlay>) (Object) Overlay.of((PropertiesOverlay) value.get(),
				name, Map.class);
		validateMap(map, nonEmpty, unique, valueValidator);
		return map;
	}

	private  void validateMap(Overlay> map, boolean nonEmpty, boolean unique,
			Validator valueValidator) {
		new MapValidator(valueValidator).validate(map);
		checkMapNotEmpty(map, nonEmpty);
		checkMapUnique(map, unique);
	}

	private  void checkMapNotEmpty(Overlay> list, boolean nonEmpty) {
		if (nonEmpty) {
			MapOverlay mapOverlay = Overlay.getMapOverlay(list);
			if (list != null && !list.isPresent()) {
				if (nonEmpty && mapOverlay.size() == 0) {
					results.addError(msg(EmptyList), list);
				}
			}
		}
	}

	private  void checkMapUnique(Overlay> map, boolean unique) {
		if (unique) {
			MapOverlay mapOverlay = Overlay.getMapOverlay(map);
			Set seen = new HashSet<>();
			for (String key : mapOverlay.keySet()) {
				X value = mapOverlay.get(key);
				if (seen.contains(value)) {
					results.addError(msg(DuplicateValue, value, key), Overlay.of(mapOverlay, key));
				} else {
					seen.add(value);
				}
			}
		}
	}

	void checkMissing(Overlay field, boolean required) {
		if (required && (field == null || !field.isPresent())) {
			results.addError(msg(MissingField, field.getPathInParent()), value);
		}
	}

	public Overlay> validateExtensions(Map extensions) {
		return validateExtensions(extensions, null);
	}

	public Overlay> validateExtensions(Map extensions, String crumb) {
		Overlay> mapOverlay = Overlay.of(extensions);
		validateMap(mapOverlay, false, false, null);
		return mapOverlay;
	}

	public Overlay validateFormatField(String name, boolean required, String type) {
		Overlay field = validateStringField(name, required);
		if (field != null && field.isPresent()) {
			String normalType = null;
			switch (field.get()) {
			case "int32":
			case "int64":
				normalType = "integer";
				break;
			case "float":
			case "double":
				normalType = "number";
				break;
			case "byte":
			case "binary":
			case "date":
			case "date-time":
			case "password":
				normalType = "string";
			}
			if (normalType != null) {
				if (type == null || !type.equals(normalType)) {
					results.addWarning(msg(BaseValidationMessages.WrongTypeFormat, field, type, normalType), field);
				}
			}
		}
		return field;
	}

	public void checkDefault(Overlay overlay, String type) {
		if (overlay != null && overlay.isPresent() && type != null) {
			Object defaultValue = overlay.get();
			boolean ok = false;
			switch (type) {
			case "string":
				ok = defaultValue instanceof String;
				break;
			case "number":
				ok = NumericUtils.isNumeric(defaultValue);
				break;
			case "integer":
				ok = NumericUtils.isIntegral(defaultValue);
				break;
			case "boolean":
				ok = defaultValue instanceof Boolean;
				break;
			case "object":
				ok = defaultValue instanceof Map;
				break;
			case "array":
				ok = defaultValue instanceof List;
				break;
			}
			if (!ok) {
				results.addError(msg(BaseValidationMessages.WrongTypeValue, type, defaultValue), overlay);
			}
		}
	}

	public void checkJsonType(Overlay value, Collection> allowedJsonTypes,
			ValidationResults results) {
		JsonNode json = value.getParsedJson();
		if (json != null && !json.isMissingNode()) {
			for (Class type : allowedJsonTypes) {
				if (type.isAssignableFrom(json.getClass())) {
					return;
				}
			}
			String allowed = allowedJsonTypes.stream().map(type -> getJsonValueType(type))
					.collect(Collectors.joining(", "));
			results.addError(msg(WrongTypeJson, getJsonValueType(json.getClass()), allowed), value);
		}
	}

	private String getJsonValueType(Class node) {
		String type = node.getSimpleName();
		return type.endsWith("Node") ? type.substring(0, type.length() - 4) : type;
	}

	protected static Map, List>> allowedJsonTypes = null;

	protected Collection> getAllowedJsonTypes(Overlay value) {
		if (allowedJsonTypes == null) {
			createAllowedJsonTypes();
		}
		JsonOverlay overlay = value.getOverlay();
		return allowedJsonTypes
				.get(overlay instanceof PropertiesOverlay ? PropertiesOverlay.class : overlay.getClass());
	}

	private static void createAllowedJsonTypes() {
		Map, List>> types = new HashMap<>();
		types.put(StringOverlay.class, Arrays.asList(TextNode.class));
		types.put(BooleanOverlay.class, Arrays.asList(BooleanNode.class));
		types.put(IntegerOverlay.class, Arrays.asList(IntNode.class, ShortNode.class, BigIntegerNode.class));
		types.put(NumberOverlay.class, Arrays.asList(NumericNode.class));
		types.put(PrimitiveOverlay.class, Arrays.asList(TextNode.class, NumericNode.class, BooleanNode.class));
		types.put(ObjectOverlay.class, Arrays.asList(JsonNode.class));
		types.put(MapOverlay.class, Arrays.asList(ObjectNode.class));
		types.put(ListOverlay.class, Arrays.asList(ArrayNode.class));
		types.put(PropertiesOverlay.class, Arrays.asList(ObjectNode.class));
		allowedJsonTypes = types;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy