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

com.github.fge.jsonschema2avro.predicates.AvroPredicates Maven / Gradle / Ivy

package com.github.fge.jsonschema2avro.predicates;

import com.fasterxml.jackson.databind.JsonNode;
import com.github.fge.jackson.NodeType;
import com.github.fge.jsonschema.library.DraftV4Library;
import com.github.fge.jsonschema.processors.validation.ArraySchemaDigester;
import com.github.fge.jsonschema.processors.validation.ObjectSchemaDigester;
import com.github.fge.jsonschema2avro.AvroPayload;
import com.google.common.base.CharMatcher;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;

import java.util.Set;

public final class AvroPredicates
{
    private static final Set KNOWN_KEYWORDS;
    private static final CharMatcher NAME_CHAR;
    private static final CharMatcher DIGIT;

    static {
        final CharMatcher letterOrUnderscore = CharMatcher.is('_')
            .or(CharMatcher.inRange('a', 'z'))
            .or(CharMatcher.inRange('A', 'Z'));
        final CharMatcher digit = CharMatcher.inRange('0', '9');

        NAME_CHAR = letterOrUnderscore.or(digit).precomputed();
        DIGIT = digit.precomputed();

        /*
         * We don't care about keywords having only syntax checkers, only about
         * keywords having an actual use in validation
         */
        KNOWN_KEYWORDS = ImmutableSet.copyOf(DraftV4Library.get()
            .getDigesters().entries().keySet());
    }

    private AvroPredicates()
    {
    }

    public static Predicate simpleType()
    {
        return new Predicate()
        {
            @Override
            public boolean apply(final AvroPayload input)
            {
                final JsonNode node = schemaNode(input);
                final NodeType type = getType(node);
                if (type == null)
                    return false;
                return type != NodeType.ARRAY && type != NodeType.OBJECT;
            }
        };
    }

    public static Predicate array()
    {
        return new Predicate()
        {
            @Override
            public boolean apply(final AvroPayload input)
            {
                final JsonNode node = schemaNode(input);
                final NodeType type = getType(node);
                if (NodeType.ARRAY != type)
                    return false;

                final JsonNode digest
                    = ArraySchemaDigester.getInstance().digest(node);

                // FIXME: I should probably make digests POJOs here
                return digest.get("hasItems").booleanValue()
                    ? !digest.get("itemsIsArray").booleanValue()
                    : digest.get("hasAdditional").booleanValue();
            }
        };
    }

    public static Predicate map()
    {
        return new Predicate()
        {
            @Override
            public boolean apply(final AvroPayload input)
            {
                final JsonNode node = schemaNode(input);
                final NodeType type = getType(node);
                if (NodeType.OBJECT != type)
                    return false;

                final JsonNode digest = ObjectSchemaDigester.getInstance()
                    .digest(node);

                // FIXME: as for array digester, the result should really be
                // a POJO
                if (!digest.get("hasAdditional").booleanValue())
                    return false;

                return digest.get("properties").size() == 0
                    && digest.get("patternProperties").size() == 0;
            }
        };
    }

    public static Predicate isEnum()
    {
        return new Predicate()
        {
            @Override
            public boolean apply(final AvroPayload input)
            {
                final JsonNode node = schemaNode(input);
                final Set set = Sets.newHashSet(node.fieldNames());
                set.retainAll(KNOWN_KEYWORDS);
                if (!set.equals(ImmutableSet.of("enum")))
                    return false;

                // Test individual entries: they must be strings, and must be
                // the same "shape" as any Avro name
                for (final JsonNode element: node.get("enum")) {
                    if (!element.isTextual())
                        return false;
                    if (!isValidAvroName(element.textValue()))
                        return false;
                }

                return true;
            }
        };
    }

    public static Predicate record()
    {
        return new Predicate()
        {
            @Override
            public boolean apply(final AvroPayload input)
            {
                final JsonNode node = schemaNode(input);
                final NodeType type = getType(node);
                if (NodeType.OBJECT != type)
                    return false;

                if (node.path("additionalProperties").asBoolean(true))
                    return false;

                if (node.has("patternProperties"))
                    return false;

                final JsonNode properties = node.path("properties");
                if (!properties.isObject())
                    return false;

                for (final String s: Sets.newHashSet(properties.fieldNames()))
                    if (!isValidAvroName(s))
                        return false;

                return true;
            }
        };
    }

    public static Predicate simpleUnion()
    {
        return new Predicate()
        {
            @Override
            public boolean apply(final AvroPayload input)
            {
                // NOTE: enums within enums are forbidden. This is tested in
                // writers, not here.
                final JsonNode node = schemaNode(input);
                final Set members = Sets.newHashSet(node.fieldNames());
                members.retainAll(KNOWN_KEYWORDS);
                return members.equals(ImmutableSet.of("anyOf"))
                    || members.equals(ImmutableSet.of("oneOf"));
            }
        };
    }

    public static Predicate typeUnion()
    {
        return new Predicate()
        {
            @Override
            public boolean apply(final AvroPayload input)
            {
                return schemaNode(input).path("type").isArray();
            }
        };
    }

    private static JsonNode schemaNode(final AvroPayload payload)
    {
        return payload.getTree().getNode();
    }

    private static NodeType getType(final JsonNode node)
    {
        final JsonNode typeNode = node.path("type");
        return typeNode.isTextual() ? NodeType.fromName(typeNode.textValue())
            : null;
    }

    private static boolean isValidAvroName(final String s)
    {
        if (s.isEmpty())
            return false;
        if (!NAME_CHAR.matchesAllOf(s))
            return false;
        return !DIGIT.matches(s.charAt(0));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy