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

io.openapiprocessor.jsonschema.schema.ResolverRef Maven / Gradle / Ivy

There is a newer version: 2024.4
Show newest version
/*
 * Copyright 2023 https://github.com/openapi-processor/openapi-parser
 * PDX-License-Identifier: Apache-2.0
 */

package io.openapiprocessor.jsonschema.schema;

import io.openapiprocessor.jsonschema.support.Types;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.net.URI;
import java.util.Collection;

public class ResolverRef {
    private final ResolverContext context;

    public ResolverRef (ResolverContext context) {
        this.context = context;
    }


    public void resolve (Bucket bucket) {
        context.addDocument(bucket.getScope(), bucket.getRawValues());
        walkBucket (bucket);
        resolve ();
    }

    @SuppressWarnings({"dereference.of.nullable"})
    private void walkBucket (Bucket bucket) {
        Scope scope = bucket.getScope ();
        JsonPointer location = bucket.getLocation ();
        SchemaVersion version = scope.getVersion ();

        bucket.forEach ((name, value) -> {
            JsonPointer propLocation = location.append (name);
            Keyword keyword = version.getKeyword (name);

            boolean navigable = isNavigable(keyword);

            if (isMetaSchema(name, value)) {
                Ref ref = createRef (scope, name, value);
                walkRef (ref, propLocation);

            } else if (isRef(name, value)) {
                Ref ref = createRef (scope, name, value);
                walkRef (ref, propLocation);

            } else if (isDynamicRef(name, value)) {
                Ref ref = createRef (scope, name, value);
                walkRef (ref, propLocation);

            } else if (isRecursiveRef(name, value)) {
                Ref ref = createRef (scope, name, value);
                walkRef (ref, propLocation);

            } else if (isNavigableObject(keyword, value)) {
                    walkSchema (scope, value, propLocation);

            } else if (isNavigableSchemaArray(keyword, value)) {
                walkSchemaArray (scope, value, propLocation);

            } else if (isNavigableSchemaMap(keyword, value)) {
                walkSchemaMap (scope, value, propLocation);

            } else if (isDefaultObject(name, value)) {
                walkSchema (scope, value, propLocation);

            } else if (keyword == null && Types.isObject (value)) {
                walkSchema (scope, value, propLocation);

            } else if (keyword == null && Types.isArray (value)) {
                walkSchemaArray (scope, value, propLocation);
            }
        });
    }

    private static boolean isDefaultObject(String name, Object value) {
        return name.equals(Keywords.DEFAULT) && Types.isObject(value);
    }

    private static boolean isNavigableSchemaMap(Keyword keyword, Object value) {
        if (keyword == null)
            return false;

        return keyword.isNavigable() && keyword.isSchemaMap() && Types.isObject(value);
    }

    private static boolean isNavigableSchemaArray(@Nullable Keyword keyword, Object value) {
        if (keyword == null)
            return false;

        return keyword.isNavigable() && keyword.isSchemaArray() && Types.isArray(value);
    }

    private static boolean isNavigableObject(@Nullable Keyword keyword, Object value) {
        if (keyword == null)
            return false;

        return  keyword.isNavigable() && keyword.isSchema() && Types.isObject(value);
    }

    private static boolean isRecursiveRef(String name, Object value) {
        return name.equals(Keywords.RECURSIVE_REF) && Types.isString(value);
    }

    private static boolean isDynamicRef(String name, Object value) {
        return name.equals(Keywords.DYNAMIC_REF) && Types.isString(value);
    }

    private static boolean isRef(String name, Object value) {
        return name.equals(Keywords.REF) && Types.isString(value);
    }

    private static boolean isMetaSchema(String name, Object value) {
        return name.equals(Keywords.SCHEMA) && Types.isString(value);
    }

    private static boolean isNavigable(Keyword keyword) {
        return keyword != null && keyword.isNavigable();
    }

    private Ref createRef (Scope scope, String name, Object value) {
        String ref = Types.convertOrNull (name, value, String.class);
        if (ref == null) {
            throw new ResolverException (String.format ("failed to resolve empty $ref in '%s'.", scope));
        }
        return new Ref (scope, ref);
    }

    private void walkRef (Ref ref, JsonPointer location) {
        Scope scope = ref.getScope ();
        URI uri = ref.getDocumentUri ();

        Object document = context.getDocument (uri);
        if (document == null) {
            // todo no auto load -> throw
            document = addDocument (scope, uri, ref);
        }

        if (!context.isProcessedDocument (uri)) {
            context.setProcessedDocument (uri);

            Scope docScope = scope.move (uri, document);  // todo document != null
            Bucket bucket = Bucket.createBucket(docScope, document, JsonPointer.empty());
            if (bucket == null) {
                return; // todo error
            }

            walkIds (bucket);
            walkBucket (bucket);
        }

        addReference (ref);
    }

    private void addReference (Ref ref) {
        context.addRef (ref);
    }

    private void walkIds (Bucket bucket) {
        ResolverId resolverId = new ResolverId (context);
        resolverId.resolve(bucket);
    }

    private void walkSchema (Scope currentScope, Object value, JsonPointer location) {
        Scope scope = currentScope.move (value);
        Bucket bucket = Bucket.createBucket(scope, value, location);
        if (bucket == null) {
            return; // todo error
        }

        walkBucket (bucket);
    }

    private void walkSchemaArray (Scope currentScope, Object value, JsonPointer location) {
        Collection items = Types.asArray (value);
        if (items == null) {
            return; // todo error
        }

        int index = 0;
        for (Object item : items) {
            JsonPointer itemLocation = location.append (index);
            walkSchema (currentScope, item, itemLocation);
            index++;
        }
    }

    private void walkSchemaMap (Scope currentScope, Object value, JsonPointer location) {
        Scope targetScope = currentScope.move (value);
        Bucket bucket = Bucket.createBucket(targetScope, value, location);
        if (bucket == null) {
            return; // // todo error
        }

        bucket.forEach ((propName, propValue) -> {
            JsonPointer propLocation = location.append (propName);
            walkSchema (targetScope, propValue, propLocation);
        });
    }

    private void resolve () {
        context.references.each ((Ref ref) -> {
            // $ref == $id?
            URI id = ref.getAbsoluteUri ();
            Object idDocument = context.getDocument (id);
            if (idDocument != null) {
                Scope docScope = ref.getScope ().move (id, idDocument);
                context.addRef (ref, docScope, idDocument);
                return;
            }

            // no, try to resolve by document and pointer
            URI documentUri = ref.getDocumentUri ();
            Object document = context.getDocument (documentUri);
            // todo check null
            Scope scope = ref.getScope ().move (documentUri, document);
            Bucket bucket = Bucket.createBucket(scope, document);

            // no object -> to (simple) value
            if (bucket == null) {
                assert document != null;
                context.addRef (ref, scope, document);
                return;
            }

            // object, no pointer -> to document
            if (!ref.hasPointer ()) {
                context.addRef (ref, scope, document);
                return;
            }

            RawValue referenced = bucket.getRawValue (JsonPointer.from (ref.getPointer ()));
            if (referenced == null) {
                throw new ResolverException (String.format ("failed to resolve ref <%s/%s>.", documentUri, ref));
            }

            context.addRef (ref, referenced.getScope (), referenced.getValue ()); // todo value is null ?
        });
    }

    private @Nullable Object addDocument (Scope scope, URI uri, Ref ref) {
        if (!ref.hasDocument ())
            return null;

        return context.addDocument (uri, scope.getDocumentUri ().toString (), ref.getRef ());
    }
}