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

com.networknt.schema.BaseJsonValidator Maven / Gradle / Ivy

There is a newer version: 1.5.3
Show newest version
/*
 * Copyright (c) 2016 Network New Technologies Inc.
 *
 * 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 com.networknt.schema;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.networknt.schema.annotation.JsonNodeAnnotation;
import com.networknt.schema.i18n.DefaultMessageSource;

import org.slf4j.Logger;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

public abstract class BaseJsonValidator extends ValidationMessageHandler implements JsonValidator {
    protected final boolean suppressSubSchemaRetrieval;

    protected final JsonNode schemaNode;

    protected ValidationContext validationContext;

    public BaseJsonValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
            JsonSchema parentSchema, ValidatorTypeCode validatorType, ValidationContext validationContext) {
        this(schemaLocation, evaluationPath, schemaNode, parentSchema, validatorType, validatorType, validationContext,
                false);
    }

    public BaseJsonValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
            JsonSchema parentSchema, ErrorMessageType errorMessageType, Keyword keyword,
            ValidationContext validationContext, boolean suppressSubSchemaRetrieval) {
        super(errorMessageType,
                (validationContext != null && validationContext.getConfig() != null)
                        ? validationContext.getConfig().isCustomMessageSupported()
                        : true,
                (validationContext != null && validationContext.getConfig() != null)
                        ? validationContext.getConfig().getMessageSource()
                        : DefaultMessageSource.getInstance(),
                keyword,
                parentSchema, schemaLocation, evaluationPath);
        this.validationContext = validationContext;
        this.schemaNode = schemaNode;
        this.suppressSubSchemaRetrieval = suppressSubSchemaRetrieval;
    }

    /**
     * Copy constructor.
     * 
     * @param copy to copy from
     */
    protected BaseJsonValidator(BaseJsonValidator copy) {
        super(copy);
        this.suppressSubSchemaRetrieval = copy.suppressSubSchemaRetrieval;
        this.schemaNode = copy.schemaNode;
        this.validationContext = copy.validationContext;
    }

    private static JsonSchema obtainSubSchemaNode(final JsonNode schemaNode, final ValidationContext validationContext) {
        final JsonNode node = schemaNode.get("id");

        if (node == null) {
            return null;
        }

        if (node.equals(schemaNode.get("$schema"))) {
            return null;
        }

        final String text = node.textValue();
        if (text == null) {
            return null;
        }

        final SchemaLocation schemaLocation = SchemaLocation.of(node.textValue());

        return validationContext.getJsonSchemaFactory().getSchema(schemaLocation, validationContext.getConfig());
    }

    protected static boolean equals(double n1, double n2) {
        return Math.abs(n1 - n2) < 1e-12;
    }

    protected static void debug(Logger logger, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
        logger.debug("validate( {}, {}, {})", node, rootNode, instanceLocation);
    }

    /**
     * Checks based on the current {@link DiscriminatorContext} whether the provided {@link JsonSchema} a match against
     * against the current discriminator.
     *
     * @param currentDiscriminatorContext the currently active {@link DiscriminatorContext}
     * @param discriminator               the discriminator to use for the check
     * @param discriminatorPropertyValue  the value of the discriminator/propertyName field
     * @param jsonSchema                  the {@link JsonSchema} to check
     */
    protected static void checkDiscriminatorMatch(final DiscriminatorContext currentDiscriminatorContext,
                                                  final ObjectNode discriminator,
                                                  final String discriminatorPropertyValue,
                                                  final JsonSchema jsonSchema) {
        if (discriminatorPropertyValue == null) {
            currentDiscriminatorContext.markMatch();
            return;
        }

        final JsonNode discriminatorMapping = discriminator.get("mapping");
        if (null == discriminatorMapping) {
            checkForImplicitDiscriminatorMappingMatch(currentDiscriminatorContext,
                    discriminatorPropertyValue,
                    jsonSchema);
        } else {
            checkForExplicitDiscriminatorMappingMatch(currentDiscriminatorContext,
                    discriminatorPropertyValue,
                    discriminatorMapping,
                    jsonSchema);
            if (!currentDiscriminatorContext.isDiscriminatorMatchFound()
                    && noExplicitDiscriminatorKeyOverride(discriminatorMapping, jsonSchema)) {
                checkForImplicitDiscriminatorMappingMatch(currentDiscriminatorContext,
                        discriminatorPropertyValue,
                        jsonSchema);
            }
        }
    }

    /**
     * Rolls up all nested and compatible discriminators to the root discriminator of the type. Detects attempts to redefine
     * the propertyName or mappings.
     *
     * @param currentDiscriminatorContext the currently active {@link DiscriminatorContext}
     * @param discriminator               the discriminator to use for the check
     * @param schema                      the value of the discriminator/propertyName field
     * @param instanceLocation                          the logging prefix
     */
    protected static void registerAndMergeDiscriminator(final DiscriminatorContext currentDiscriminatorContext,
                                                        final ObjectNode discriminator,
                                                        final JsonSchema schema,
                                                        final JsonNodePath instanceLocation) {
        final JsonNode discriminatorOnSchema = schema.schemaNode.get("discriminator");
        if (null != discriminatorOnSchema && null != currentDiscriminatorContext
                .getDiscriminatorForPath(schema.schemaLocation)) {
            // this is where A -> B -> C inheritance exists, A has the root discriminator and B adds to the mapping
            final JsonNode propertyName = discriminatorOnSchema.get("propertyName");
            if (null != propertyName) {
                throw new JsonSchemaException(instanceLocation + " schema " + schema + " attempts redefining the discriminator property");
            }
            final ObjectNode mappingOnContextDiscriminator = (ObjectNode) discriminator.get("mapping");
            final ObjectNode mappingOnCurrentSchemaDiscriminator = (ObjectNode) discriminatorOnSchema.get("mapping");
            if (null == mappingOnContextDiscriminator && null != mappingOnCurrentSchemaDiscriminator) {
                // here we have a mapping on a nested discriminator and none on the root discriminator, so we can simply
                // make it the root's
                discriminator.set("mapping", discriminatorOnSchema);
            } else if (null != mappingOnContextDiscriminator && null != mappingOnCurrentSchemaDiscriminator) {
                // here we have to merge. The spec doesn't specify anything on this, but here we don't accept redefinition of
                // mappings that already exist
                final Iterator> fieldsToAdd = mappingOnCurrentSchemaDiscriminator.fields();
                while (fieldsToAdd.hasNext()) {
                    final Map.Entry fieldToAdd = fieldsToAdd.next();
                    final String mappingKeyToAdd = fieldToAdd.getKey();
                    final JsonNode mappingValueToAdd = fieldToAdd.getValue();

                    final JsonNode currentMappingValue = mappingOnContextDiscriminator.get(mappingKeyToAdd);
                    if (null != currentMappingValue && currentMappingValue != mappingValueToAdd) {
                        throw new JsonSchemaException(instanceLocation + "discriminator mapping redefinition from " + mappingKeyToAdd
                                + "/" + currentMappingValue + " to " + mappingValueToAdd);
                    } else if (null == currentMappingValue) {
                        mappingOnContextDiscriminator.set(mappingKeyToAdd, mappingValueToAdd);
                    }
                }
            }
        }
        currentDiscriminatorContext.registerDiscriminator(schema.schemaLocation, discriminator);
    }

    private static void checkForImplicitDiscriminatorMappingMatch(final DiscriminatorContext currentDiscriminatorContext,
                                                                  final String discriminatorPropertyValue,
                                                                  final JsonSchema schema) {
        if (schema.schemaLocation.getFragment().getName(-1).equals(discriminatorPropertyValue)) {
            currentDiscriminatorContext.markMatch();
        }
    }

    private static void checkForExplicitDiscriminatorMappingMatch(final DiscriminatorContext currentDiscriminatorContext,
                                                                  final String discriminatorPropertyValue,
                                                                  final JsonNode discriminatorMapping,
                                                                  final JsonSchema schema) {
        final Iterator> explicitMappings = discriminatorMapping.fields();
        while (explicitMappings.hasNext()) {
            final Map.Entry candidateExplicitMapping = explicitMappings.next();
            if (candidateExplicitMapping.getKey().equals(discriminatorPropertyValue)
                    && ("#" + schema.schemaLocation.getFragment().toString())
                            .equals(candidateExplicitMapping.getValue().asText())) {
                currentDiscriminatorContext.markMatch();
                break;
            }
        }
    }

    private static boolean noExplicitDiscriminatorKeyOverride(final JsonNode discriminatorMapping,
                                                              final JsonSchema parentSchema) {
        final Iterator> explicitMappings = discriminatorMapping.fields();
        while (explicitMappings.hasNext()) {
            final Map.Entry candidateExplicitMapping = explicitMappings.next();
            if (candidateExplicitMapping.getValue().asText()
                    .equals(parentSchema.schemaLocation.getFragment().toString())) {
                return false;
            }
        }
        return true;
    }

    @Override
    public SchemaLocation getSchemaLocation() {
        return this.schemaLocation;
    }

    @Override
    public JsonNodePath getEvaluationPath() {
        return this.evaluationPath;
    }

    @Override
    public String getKeyword() {
        return this.keyword.getValue();
    }

    public JsonNode getSchemaNode() {
        return this.schemaNode;
    }

    /**
     * Gets the parent schema.
     * 

* This is the lexical parent schema. * * @return the parent schema */ public JsonSchema getParentSchema() { return this.parentSchema; } /** * Gets the evaluation parent schema. *

* This is the dynamic parent schema when following references. * * @see JsonSchema#fromRef(JsonSchema, JsonNodePath) * @return the evaluation parent schema */ public JsonSchema getEvaluationParentSchema() { if (this.evaluationParentSchema != null) { return this.evaluationParentSchema; } return getParentSchema(); } protected JsonSchema fetchSubSchemaNode(ValidationContext validationContext) { return this.suppressSubSchemaRetrieval ? null : obtainSubSchemaNode(this.schemaNode, validationContext); } public Set validate(ExecutionContext executionContext, JsonNode node) { return validate(executionContext, node, node, atRoot()); } protected String getNodeFieldType() { JsonNode typeField = this.getParentSchema().getSchemaNode().get("type"); if (typeField != null) { return typeField.asText(); } return null; } protected void preloadJsonSchemas(final Collection schemas) { for (final JsonSchema schema : schemas) { schema.initializeValidators(); } } /** * Get the root path. * * @return The path. */ protected JsonNodePath atRoot() { return new JsonNodePath(this.validationContext.getConfig().getPathType()); } @Override public String toString() { return getEvaluationPath().getName(-1); } /** * Determines if the keyword exists adjacent in the evaluation path. *

* This does not check if the keyword exists in the current meta schema as this * can be a cross-draft case where the properties keyword is in a Draft 7 schema * and the unevaluatedProperties keyword is in an outer Draft 2020-12 schema. *

* The fact that the validator exists in the evaluation path implies that the * keyword was valid in whatever meta schema for that schema it was created for. * * @param keyword the keyword to check * @return true if found */ protected boolean hasAdjacentKeywordInEvaluationPath(String keyword) { boolean hasValidator = false; JsonSchema schema = getEvaluationParentSchema(); while (schema != null) { for (JsonValidator validator : schema.getValidators()) { if (keyword.equals(validator.getKeyword())) { hasValidator = true; break; } } if (hasValidator) { break; } schema = schema.getEvaluationParentSchema(); } return hasValidator; } @Override protected MessageSourceValidationMessage.Builder message() { return super.message().schemaNode(this.schemaNode); } /** * Determine if annotations should be reported. * * @param executionContext the execution context * @return true if annotations should be reported */ protected boolean collectAnnotations(ExecutionContext executionContext) { return collectAnnotations(executionContext, getKeyword()); } /** * Determine if annotations should be reported. * * @param executionContext the execution context * @param keyword the keyword * @return true if annotations should be reported */ protected boolean collectAnnotations(ExecutionContext executionContext, String keyword) { return executionContext.getExecutionConfig().isAnnotationCollectionEnabled() && executionContext.getExecutionConfig().getAnnotationCollectionFilter().test(keyword); } /** * Puts an annotation. * * @param executionContext the execution context * @param customizer to customize the annotation */ protected void putAnnotation(ExecutionContext executionContext, Consumer customizer) { JsonNodeAnnotation.Builder builder = JsonNodeAnnotation.builder().evaluationPath(this.evaluationPath) .schemaLocation(this.schemaLocation).keyword(getKeyword()); customizer.accept(builder); executionContext.getAnnotations().put(builder.build()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy