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

shade.com.alibaba.fastjson2.schema.StringSchema Maven / Gradle / Ivy

There is a newer version: 1.3.7
Show newest version
package com.alibaba.fastjson2.schema;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.alibaba.fastjson2.util.DateUtils;
import com.alibaba.fastjson2.util.TypeUtils;

import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class StringSchema
        extends JSONSchema {
    static final Pattern EMAIL_PATTERN = Pattern.compile("^\\s*?(.+)@(.+?)\\s*$");
    static final Pattern IP_DOMAIN_PATTERN = Pattern.compile("^\\[(.*)\\]$");
    static final Pattern USER_PATTERN = Pattern.compile("^\\s*(((\\\\.)|[^\\s\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]]|')+|(\"[^\"]*\"))(\\.(((\\\\.)|[^\\s\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]]|')+|(\"[^\"]*\")))*$");

    final int maxLength;
    final int minLength;
    final String format;
    final String patternFormat;
    final Pattern pattern;
    final boolean typed;
    final AnyOf anyOf;
    final OneOf oneOf;
    final String constValue;
    final Set enumValues;

    final Predicate formatValidator;

    StringSchema(JSONObject input) {
        super(input);
        this.typed = "string".equalsIgnoreCase(input.getString("type"));
        this.minLength = input.getIntValue("minLength", -1);
        this.maxLength = input.getIntValue("maxLength", -1);
        this.patternFormat = input.getString("pattern");
        this.pattern = patternFormat == null ? null : Pattern.compile(patternFormat);
        this.format = input.getString("format");

        Object anyOf = input.get("anyOf");
        if (anyOf instanceof JSONArray) {
            this.anyOf = anyOf((JSONArray) anyOf, String.class);
        } else {
            this.anyOf = null;
        }

        Object oneOf = input.get("oneOf");
        if (oneOf instanceof JSONArray) {
            this.oneOf = oneOf((JSONArray) oneOf, String.class);
        } else {
            this.oneOf = null;
        }

        this.constValue = input.getString("const");

        {
            Set enumValues = null;
            Object property = input.get("enum");
            if (property instanceof Collection) {
                Collection enums = (Collection) property;
                enumValues = new LinkedHashSet<>(enums.size());
                enumValues.addAll((Collection) enums);
            } else if (property instanceof Object[]) {
                enumValues = input.getObject("enum", TypeReference.collectionType(LinkedHashSet.class, String.class));
            }
            this.enumValues = enumValues;
        }

        if (format == null) {
            formatValidator = null;
        } else {
            switch (format) {
                case "email":
                    formatValidator = StringSchema::isEmail;
                    break;
                case "ipv4":
                    formatValidator = TypeUtils::validateIPv4;
                    break;
                case "ipv6":
                    formatValidator = TypeUtils::validateIPv6;
                    break;
                case "uri":
                    formatValidator = url -> {
                        if (url == null || url.isEmpty()) {
                            return false;
                        }

                        try {
                            new URI(url);
                            return true;
                        } catch (URISyntaxException ignored) {
                            return false;
                        }
                    };
                    break;
                case "date-time":
                    formatValidator = DateUtils::isDate;
                    break;
                case "date":
                    formatValidator = DateUtils::isLocalDate;
                    break;
                case "time":
                    formatValidator = DateUtils::isLocalTime;
                    break;
                case "duration":
                    formatValidator = str -> {
                        if (str == null || str.isEmpty()) {
                            return false;
                        }

                        try {
                            Duration.parse(str);
                            return true;
                        } catch (DateTimeParseException ignored) {
                            return false;
                        }
                    };
                    break;
                case "uuid":
                    formatValidator = TypeUtils::isUUID;
                    break;
                default:
                    formatValidator = null;
                    break;
            }
        }
    }

    @Override
    public Type getType() {
        return Type.String;
    }

    @Override
    public ValidateResult validate(Object value) {
        if (value == null) {
            if (typed) {
                return REQUIRED_NOT_MATCH;
            }
            return SUCCESS;
        }

        if (value instanceof String) {
            String str = (String) value;

            if (minLength >= 0 || maxLength >= 0) {
                int count = str.codePointCount(0, str.length());
                if (minLength >= 0 && count < minLength) {
                    return new ValidateResult(false, "minLength not match, expect >= %s, but %s", minLength, str.length());
                }

                if (maxLength >= 0 && count > maxLength) {
                    return new ValidateResult(false, "maxLength not match, expect <= %s, but %s", maxLength, str.length());
                }
            }

            if (pattern != null) {
                if (!pattern.matcher(str).find()) {
                    return new ValidateResult(false, "pattern not match, expect %s, but %s", patternFormat, str);
                }
            }

            if (formatValidator != null) {
                if (!formatValidator.test(str)) {
                    return new ValidateResult(false, "format not match, expect %s, but %s", format, str);
                }
            }

            if (anyOf != null) {
                ValidateResult result = anyOf.validate(str);
                if (!result.isSuccess()) {
                    return result;
                }
            }

            if (oneOf != null) {
                ValidateResult result = oneOf.validate(str);
                if (!result.isSuccess()) {
                    return result;
                }
            }

            if (constValue != null) {
                if (!constValue.equals(str)) {
                    return new ValidateResult(false, "must be const %s, but %s", constValue, str);
                }
            }

            if (enumValues != null) {
                if (!enumValues.contains(str)) {
                    return new ValidateResult(false, "not in enum values, %s", str);
                }
            }

            return SUCCESS;
        }

        if (!typed) {
            return SUCCESS;
        }

        return new ValidateResult(false, "expect type %s, but %s", Type.String, value.getClass());
    }

    public static boolean isEmail(String email) {
        if (email == null) {
            return false;
        }

        if (email.endsWith(".")) { // check this first - it's cheap!
            return false;
        }

        // Check the whole email address structure
        Matcher emailMatcher = EMAIL_PATTERN.matcher(email);
        if (!emailMatcher.matches()) {
            return false;
        }

        String user = emailMatcher.group(1);
        if (user.length() > 64) {
            return false;
        }

        if (!USER_PATTERN.matcher(user).matches()) {
            return false;
        }

        String domain = emailMatcher.group(2);
        Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain);

        boolean validDomain;
        if (ipDomainMatcher.matches()) {
            String inetAddress = ipDomainMatcher.group(1);
            validDomain = TypeUtils.validateIPv4(inetAddress) || TypeUtils.validateIPv6(inetAddress);
        } else {
            validDomain = DomainValidator.isValid(domain) || DomainValidator.isValidTld(domain);
        }

        return validDomain;
    }

    @Override
    public JSONObject toJSONObject() {
        JSONObject object = new JSONObject();

        object.put("type", "string");

        if (minLength != -1) {
            object.put("minLength", minLength);
        }

        if (format != null) {
            object.put("format", format);
        }

        if (patternFormat != null) {
            object.put("pattern", pattern);
        }

        if (anyOf != null) {
            object.put("anyOf", anyOf);
        }

        if (oneOf != null) {
            object.put("oneOf", oneOf);
        }

        if (constValue != null) {
            object.put("const", constValue);
        }

        if (enumValues != null && !enumValues.isEmpty()) {
            object.put("enum", enumValues);
        }

        return object;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy