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

io.nats.client.support.Validator Maven / Gradle / Ivy

The newest version!
// Copyright 2020 The NATS Authors
// 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 io.nats.client.support;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.regex.Pattern;

import static io.nats.client.support.NatsConstants.DOT;
import static io.nats.client.support.NatsJetStreamConstants.MAX_HISTORY_PER_KEY;

@SuppressWarnings("UnusedReturnValue")
public abstract class Validator {
    private Validator() {} /* ensures cannot be constructed */

    /*
        cannot contain spaces \r \n \t
        cannot start or end with subject token delimiter .
        some things don't allow it to end greater
    */
    public static String validateSubjectTerm(String subject, String label, boolean required) {
        subject = emptyAsNull(subject);
        if (subject == null) {
            if (required) {
                throw new IllegalArgumentException(label + " cannot be null or empty.");
            }
            return null;
        }
        if (subject.endsWith(".")) {
            throw new IllegalArgumentException(label + " cannot end with '.'");
        }

        String[] segments = subject.split("\\.");
        for (int seg = 0; seg < segments.length; seg++) {
            String segment = segments[seg];
            int sl = segment.length();
            if (sl == 0) {
                if (seg == 0) {
                    throw new IllegalArgumentException(label + " cannot start with '.'");
                }
                throw new IllegalArgumentException(label + " segment cannot be empty");
            }
            else {
                for (int m = 0; m < sl; m++) {
                    char c = segment.charAt(m);
                    switch (c) {
                        case 32:
                        case '\r':
                        case '\n':
                        case '\t':
                            throw new IllegalArgumentException(label + " cannot contain space, tab, carriage return or linefeed character");
                        case '*':
                            if (sl != 1) {
                                throw new IllegalArgumentException(label + " wildcard improperly placed.");
                            }
                            break;
                        case '>':
                            if (sl != 1 || (seg + 1 != segments.length)) {
                                throw new IllegalArgumentException(label + " wildcard improperly placed.");
                            }
                            break;
                    }
                }
            }
        }
        return subject;
    }

    public static String validateSubject(String s, boolean required) {
        return validateSubjectTerm(s, "Subject", required);
    }

    public static String validateSubject(String subject, String label, boolean required, boolean cantEndWithGt) {
        subject = validateSubjectTerm(subject, label, required);
        if (subject != null && cantEndWithGt && subject.endsWith(".>")) {
            throw new IllegalArgumentException(label + " last segment cannot be '>'");
        }
        return subject;
    }

    public static String validateReplyTo(String s, boolean required) {
        return validatePrintableExceptWildGt(s, "Reply To", required);
    }

    public static String validateQueueName(String s, boolean required) {
        return validateSubjectTerm(s, "QueueName", required);
    }

    public static String validateStreamName(String s, boolean required) {
        return validatePrintableExceptWildDotGtSlashes(s, "Stream", required);
    }

    public static String validateDurable(String s, boolean required) {
        return validatePrintableExceptWildDotGtSlashes(s, "Durable", required);
    }

    public static String validateConsumerName(String s, boolean required) {
        return validatePrintableExceptWildDotGtSlashes(s, "Name", required);
    }

    public static String validatePrefixOrDomain(String s, String label, boolean required) {
        return _validate(s, required, label, () -> {
            if (s.startsWith(DOT)) {
                throw new IllegalArgumentException(label + " cannot start with '.' [" + s + "]");
            }
            if (notPrintableOrHasWildGt(s)) {
                throw new IllegalArgumentException(label + " must be in the printable ASCII range and cannot include '*', '>' [" + s + "]");
            }
            return s;
        });
    }

    public static List validateKvKeysWildcardAllowedRequired(List keys) {
        required(keys, "Key");
        for (String key : keys) {
            validateWildcardKvKey(key, "Key", true);
        }
        return keys;
    }

    public static String validateKvKeyWildcardAllowedRequired(String s) {
        return validateWildcardKvKey(s, "Key", true);
    }

    public static String validateNonWildcardKvKeyRequired(String s) {
        return validateNonWildcardKvKey(s, "Key", true);
    }

    public static void validateNotSupplied(String s, NatsJetStreamClientError err) {
        if (!nullOrEmpty(s)) {
            throw err.instance();
        }
    }

    public static String validateMustMatchIfBothSupplied(String s1, String s2, NatsJetStreamClientError err) {
        // s1   | s2   || result
        // ---- | ---- || --------------
        // null | null || valid, null s2
        // null | y    || valid, y s2
        // x    | null || valid, x s1
        // x    | x    || valid, x s1
        // x    | y    || invalid
        s1 = emptyAsNull(s1);
        s2 = emptyAsNull(s2);
        if (s1 == null) {
            return s2; // s2 can be either null or y
        }

        // x / null or x / x
        if (s2 == null || s1.equals(s2)) {
            return s1;
        }

        throw err.instance();
    }

    public static String required(String s, String label) {
        if (emptyAsNull(s) == null) {
            throw new IllegalArgumentException(label + " cannot be null or empty.");
        }
        return s;
    }

    @Deprecated
    public static String required(String s1, String s2, String label) {
        if (emptyAsNull(s1) == null || emptyAsNull(s2) == null) {
            throw new IllegalArgumentException(label + " cannot be null or empty.");
        }
        return s1;
    }

    public static  T required(T o, String label) {
        if (o == null) {
            throw new IllegalArgumentException(label + " cannot be null.");
        }
        return o;
    }

    public static void required(List l, String label) {
        if (l == null || l.isEmpty()) {
            throw new IllegalArgumentException(label + " cannot be null or empty.");
        }
    }

    public static void required(Map m, String label) {
        if (m == null || m.isEmpty()) {
            throw new IllegalArgumentException(label + " cannot be null or empty.");
        }
    }

    public static String _validate(String s, boolean required, String label, Supplier customValidate) {
        if (emptyAsNull(s) == null) {
            if (required) {
                throw new IllegalArgumentException(label + " cannot be null or empty.");
            }
            return null;
        }
        return customValidate.get();
    }

    public static String validateMaxLength(String s, int maxLength, boolean required, String label) {
        return _validate(s, required, label, () -> {
            int len = s.getBytes(StandardCharsets.UTF_8).length;
            if (len > maxLength) {
                throw new IllegalArgumentException(label + " cannot be longer than " + maxLength + " bytes but was " + len + " bytes");
            }
            return s;
        });
    }

    public static String validatePrintable(String s, String label, boolean required) {
        return _validate(s, required, label, () -> {
            if (notPrintable(s)) {
                throw new IllegalArgumentException(label + " must be in the printable ASCII range [" + s + "]");
            }
            return s;
        });
    }

    public static String validatePrintableExceptWildDotGt(String s, String label, boolean required) {
        return _validate(s, required, label, () -> {
            if (notPrintableOrHasWildGtDot(s)) {
                throw new IllegalArgumentException(label + " must be in the printable ASCII range and cannot include '*', '.' or '>' [" + s + "]");
            }
            return s;
        });
    }

    public static String validatePrintableExceptWildDotGtSlashes(String s, String label, boolean required) {
        return _validate(s, required, label, () -> {
            if (notPrintableOrHasWildGtDotSlashes(s)) {
                throw new IllegalArgumentException(label + " must be in the printable ASCII range and cannot include '*', '.', '>', '\\' or  '/' [" + s + "]");
            }
            return s;
        });
    }

    public static String validatePrintableExceptWildGt(String s, String label, boolean required) {
        return _validate(s, required, label, () -> {
            if (notPrintableOrHasWildGt(s)) {
                throw new IllegalArgumentException(label + " must be in the printable ASCII range and cannot include '*' or '>' [" + s + "]");
            }
            return s;
        });
    }

    public static String validateIsRestrictedTerm(String s, String label, boolean required) {
        return _validate(s, required, label, () -> {
            if (notRestrictedTerm(s)) {
                throw new IllegalArgumentException(label + " must only contain A-Z, a-z, 0-9, '-' or '_' [" + s + "]");
            }
            return s;
        });
    }

    public static String validateBucketName(String s, boolean required) {
        return validateIsRestrictedTerm(s, "Bucket Name", required);
    }

    public static String validateWildcardKvKey(String s, String label, boolean required) {
        return _validate(s, required, label, () -> {
            if (notWildcardKvKey(s)) {
                throw new IllegalArgumentException(label + " must only contain A-Z, a-z, 0-9, '*', '-', '_', '/', '=', '>' or '.' and cannot start with '.' [" + s + "]");
            }
            return s;
        });
    }

    public static String validateNonWildcardKvKey(String s, String label, boolean required) {
        return _validate(s, required, label, () -> {
            if (notNonWildcardKvKey(s)) {
                throw new IllegalArgumentException(label + " must only contain A-Z, a-z, 0-9, '-', '_', '/', '=' or '.' and cannot start with '.' [" + s + "]");
            }
            return s;
        });
    }

    public static long validateMaxConsumers(long max) {
        return validateGtZeroOrMinus1(max, "Max Consumers");
    }

    public static long validateMaxMessages(long max) {
        return validateGtZeroOrMinus1(max, "Max Messages");
    }

    public static long validateMaxMessagesPerSubject(long max) {
        return validateGtZeroOrMinus1(max, "Max Messages Per Subject");
    }

    public static int validateMaxHistory(int max) {
        if (max < 1 || max > MAX_HISTORY_PER_KEY) {
            throw new IllegalArgumentException("Max History must be from 1 to " + MAX_HISTORY_PER_KEY + " inclusive.");
        }
        return max;
    }

    public static long validateMaxBytes(long max) {
        return validateGtZeroOrMinus1(max, "Max Bytes");
    }

    public static long validateMaxBucketBytes(long max) {
        return validateGtZeroOrMinus1(max, "Max Bucket Bytes"); // max bucket bytes is a kv alias to max bytes
    }

    private static long validateMaxMessageSize(long max, String label) {
        long l = validateGtZeroOrMinus1(max, label);
        if (l > Integer.MAX_VALUE) {
            throw new IllegalArgumentException(label + " cannot be larger than " + Integer.MAX_VALUE);
        }
        return l;
    }

    public static long validateMaxMessageSize(long max) {
        return validateMaxMessageSize(max, "Max Message Size");
    }

    public static long validateMaxValueSize(long max) {
        return validateMaxMessageSize(max, "Max Value Size"); // max value size is a kv alias to max message size
    }

    public static int validateNumberOfReplicas(int replicas) {
        if (replicas < 1 || replicas > 5) {
            throw new IllegalArgumentException("Replicas must be from 1 to 5 inclusive.");
        }
        return replicas;
    }

    public static Duration validateDurationRequired(Duration d) {
        if (d == null || d.isZero() || d.isNegative()) {
            throw new IllegalArgumentException("Duration required and must be greater than 0.");
        }
        return d;
    }

    public static Duration validateDurationNotRequiredGtOrEqZero(Duration d, Duration ifNull) {
        if (d == null) {
            return ifNull;
        }
        if (d.isNegative()) {
            throw new IllegalArgumentException("Duration must be greater than or equal to 0.");
        }
        return d;
    }

    public static Duration validateDurationNotRequiredGtOrEqZero(long millis) {
        if (millis < 0) {
            throw new IllegalArgumentException("Duration must be greater than or equal to 0.");
        }
        return Duration.ofMillis(millis);
    }

    public static String validateNotNull(String s, String fieldName) {
        if (s == null) {
            throw new IllegalArgumentException(fieldName + " cannot be null");
        }
        return s;
    }

    public static Object validateNotNull(Object o, String fieldName) {
        if (o == null) {
            throw new IllegalArgumentException(fieldName + " cannot be null");
        }
        return o;
    }

    public static int validateGtZero(int i, String label) {
        if (i < 1) {
            throw new IllegalArgumentException(label + " must be greater than zero");
        }
        return i;
    }

    public static long validateGtZero(long l, String label) {
        if (l < 1) {
            throw new IllegalArgumentException(label + " must be greater than zero");
        }
        return l;
    }

    public static long validateGtZeroOrMinus1(long l, String label) {
        if (zeroOrLtMinus1(l)) {
            throw new IllegalArgumentException(label + " must be greater than zero or -1 for unlimited");
        }
        return l;
    }

    public static long validateGtEqMinus1(long l, String label) {
        if (l < -1) {
            throw new IllegalArgumentException(label + " must be greater than zero or -1 for unlimited");
        }
        return l;
    }

    public static long validateNotNegative(long l, String label) {
        if (l < 0) {
            throw new IllegalArgumentException(label + " cannot be negative");
        }
        return l;
    }

    public static boolean isGtEqZero(long l) {
        return l >= 0;
    }

    public static long validateGtEqZero(long l, String label) {
        if (l < 0) {
            throw new IllegalArgumentException(label + " must be greater than or equal to zero");
        }
        return l;
    }

    // ----------------------------------------------------------------------------------------------------
    // Helpers
    // ----------------------------------------------------------------------------------------------------
    public static boolean nullOrEmpty(String s) {
        return s == null || s.trim().isEmpty();
    }

    public static boolean notPrintable(String s) {
        for (int x = 0; x < s.length(); x++) {
            char c = s.charAt(x);
            if (c < 33 || c > 126) {
                return true;
            }
        }
        return false;
    }

    public static boolean notPrintableOrHasChars(String s, char[] charsToNotHave) {
        for (int x = 0; x < s.length(); x++) {
            char c = s.charAt(x);
            if (c < 33 || c > 126) {
                return true;
            }
            for (char cx : charsToNotHave) {
                if (c == cx) {
                    return true;
                }
            }
        }
        return false;
    }

    // restricted-term  = (A-Z, a-z, 0-9, dash 45, underscore 95)+
    public static boolean notRestrictedTerm(String s) {
        for (int x = 0; x < s.length(); x++) {
            char c = s.charAt(x);
            if (c < '0') { // before 0
                if (c == '-') { // only dash is accepted
                    continue;
                }
                return true; // "not"
            }
            if (c < ':') {
                continue; // means it's 0 - 9
            }
            if (c < 'A') {
                return true; // between 9 and A is "not restricted"
            }
            if (c < '[') {
                continue; // means it's A - Z
            }
            if (c < 'a') { // before a
                if (c == '_') { // only underscore is accepted
                    continue;
                }
                return true; // "not"
            }
            if (c > 'z') { // 122 is z, characters after of them are "not restricted"
                return true;
            }
        }
        return false;
    }

    // limited-term = (A-Z, a-z, 0-9, dash 45, dot 46, fwd-slash 47, equals 61, underscore 95)+
    // kv-key-name = limited-term (dot limited-term)*
    public static boolean notNonWildcardKvKey(String s) {
        if (s.charAt(0) == '.') {
            return true; // can't start with dot
        }
        for (int x = 0; x < s.length(); x++) {
            char c = s.charAt(x);
            if (c < '0') { // before 0
                if (c == '-' || c == '.' || c == '/') { // only dash dot and fwd slash are accepted
                    continue;
                }
                return true; // "not"
            }
            if (c < ':') {
                continue; // means it's 0 - 9
            }
            if (c < 'A') {
                if (c == '=') { // equals is accepted
                    continue;
                }
                return true; // between 9 and A is "not limited"
            }
            if (c < '[') {
                continue; // means it's A - Z
            }
            if (c < 'a') { // before a
                if (c == '_') { // only underscore is accepted
                    continue;
                }
                return true; // "not"
            }
            if (c > 'z') { // 122 is z, characters after of them are "not limited"
                return true;
            }
        }
        return false;
    }

    // (A-Z, a-z, 0-9, star 42, dash 45, dot 46, fwd-slash 47, equals 61, gt 62, underscore 95)+
    public static boolean notWildcardKvKey(String s) {
        if (s.charAt(0) == '.') {
            return true; // can't start with dot
        }
        for (int x = 0; x < s.length(); x++) {
            char c = s.charAt(x);
            if (c < '0') { // before 0
                if (c == '*' || c == '-' || c == '.' || c == '/') { // only star dash dot and fwd slash are accepted
                    continue;
                }
                return true; // "not"
            }
            if (c < ':') {
                continue; // means it's 0 - 9
            }
            if (c < 'A') {
                if (c == '=' || c == '>') { // equals, gt is accepted
                    continue;
                }
                return true; // between 9 and A is "not limited"
            }
            if (c < '[') {
                continue; // means it's A - Z
            }
            if (c < 'a') { // before a
                if (c == '_') { // only underscore is accepted
                    continue;
                }
                return true; // "not"
            }
            if (c > 'z') { // 122 is z, characters after of them are "not limited"
                return true;
            }
        }
        return false;
    }

    static final char[] WILD_GT = {'*', '>'};
    static final char[] WILD_GT_DOT = {'*', '>', '.'};
    static final char[] WILD_GT_DOT_SLASHES = {'*', '>', '.', '\\', '/'};

    private static boolean notPrintableOrHasWildGt(String s) {
        return notPrintableOrHasChars(s, WILD_GT);
    }

    private static boolean notPrintableOrHasWildGtDot(String s) {
        return notPrintableOrHasChars(s, WILD_GT_DOT);
    }

    private static boolean notPrintableOrHasWildGtDotSlashes(String s) {
        return notPrintableOrHasChars(s, WILD_GT_DOT_SLASHES);
    }

    public static String emptyAsNull(String s) {
        return nullOrEmpty(s) ? null : s;
    }

    public static String emptyOrNullAs(String s, String ifEmpty) {
        return nullOrEmpty(s) ? ifEmpty : s;
    }

    public static boolean zeroOrLtMinus1(long l) {
        return l == 0 || l < -1;
    }

    public static Duration ensureNotNullAndNotLessThanMin(Duration provided, Duration minimum, Duration dflt)
    {
        return provided == null || provided.toNanos() < minimum.toNanos() ? dflt : provided;
    }

    public static Duration ensureDurationNotLessThanMin(long providedMillis, Duration minimum, Duration dflt)
    {
        return ensureNotNullAndNotLessThanMin(Duration.ofMillis(providedMillis), minimum, dflt);
    }

    public static String ensureEndsWithDot(String s) {
        return s == null || s.endsWith(DOT) ? s : s + DOT;
    }

    static final Pattern SEMVER_PATTERN = Pattern.compile("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$");

    public static String validateSemVer(String s, String label, boolean required) {
        return _validate(s, required, label, () -> {
            if (!isSemVer(s)) {
                throw new IllegalArgumentException(label + " must be a valid SemVer");
            }
            return s;
        });
    }

    public static boolean isSemVer(String s) {
        return SEMVER_PATTERN.matcher(s).find();
    }

    // This function tests filter subject equivalency
    // It does not care what order and also assumes that there are no duplicates.
    // From the server: consumer subject filters cannot overlap [10138]
    public static  boolean listsAreEquivalent(List l1, List l2)
    {
        if (l1 == null || l1.isEmpty()) {
            return l2 == null || l2.isEmpty();
        }

        if (l2 == null || l1.size() != l2.size()) {
            return false;
        }

        for (T t : l1) {
            if (!l2.contains(t)) {
                return false;
            }
        }
        return true;
    }

    public static boolean mapsAreEquivalent(Map m1, Map m2)
    {
        int s1 = m1 == null ? 0 : m1.size();
        int s2 = m2 == null ? 0 : m2.size();

        if (s1 != s2) {
            return false;
        }

        if (s1 > 0) {
            for (Map.Entry entry : m1.entrySet())
            {
                if (!entry.getValue().equals(m2.get(entry.getKey()))) {
                    return false;
                }
            }
        }

        return true;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy