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

software.amazon.smithy.jsonschema.SchemaDocument Maven / Gradle / Ivy

There is a newer version: 1.51.0
Show newest version
/*
 * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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 software.amazon.smithy.jsonschema;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.logging.Logger;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.node.NodePointer;
import software.amazon.smithy.model.node.ObjectNode;
import software.amazon.smithy.model.node.ToNode;
import software.amazon.smithy.utils.SmithyBuilder;
import software.amazon.smithy.utils.ToSmithyBuilder;

/**
 * Represents a JSON Schema document.
 *
 * @see JSON Schema specification
 */
public final class SchemaDocument implements ToNode, ToSmithyBuilder {
    private static final Logger LOGGER = Logger.getLogger(SchemaDocument.class.getName());

    private final String idKeyword;
    private final String schemaKeyword;
    private final Schema rootSchema;
    private final Map definitions;
    private final ObjectNode extensions;

    private SchemaDocument(Builder builder) {
        idKeyword = builder.idKeyword;
        schemaKeyword = builder.schemaKeyword;
        rootSchema = builder.rootSchema != null ? builder.rootSchema : Schema.builder().build();
        definitions = new LinkedHashMap<>(builder.definitions);
        extensions = builder.extensions;
    }

    /**
     * Returns a builder used to create a {@link SchemaDocument}.
     *
     * @return Returns the created builder.
     */
    public static Builder builder() {
        return new Builder();
    }

    @Override
    public Node toNode() {
        ObjectNode definitionNode = Node.objectNode();

        if (!definitions.isEmpty()) {
            // Merge in each definition using a JSON Patch style "add" operation
            // that creates missing intermediate objects.
            for (Map.Entry entry : definitions.entrySet()) {
                if (entry.getKey().startsWith("http")) {
                    LOGGER.info(() -> "Skipping the serialization of a remote schema reference: " + entry.getKey());
                } else {
                    NodePointer pointer = parseCheckedPointer(entry.getKey());
                    definitionNode = pointer
                            .addWithIntermediateValues(definitionNode, entry.getValue().toNode())
                            .expectObjectNode();
                }
            }
        }

        return Node.objectNodeBuilder()
                .withOptionalMember("$id", getIdKeyword().map(Node::from))
                .withOptionalMember("$schema", getSchemaKeyword().map(Node::from))
                .merge(rootSchema.toNode().expectObjectNode())
                .merge(extensions)
                .merge(definitionNode)
                .build()
                .withDeepSortedKeys(new SchemaComparator());
    }

    private NodePointer parseCheckedPointer(String pointer) {
        try {
            return NodePointer.parse(pointer);
        } catch (IllegalArgumentException e) {
            throw new SmithyJsonSchemaException("Invalid definition JSON pointer: " + e.getMessage(), e);
        }
    }

    @Override
    public Builder toBuilder() {
        Builder builder = builder()
                .idKeyword(idKeyword)
                .schemaKeyword(schemaKeyword)
                .rootSchema(rootSchema)
                .extensions(extensions);
        definitions.forEach(builder::putDefinition);
        return builder;
    }

    /**
     * Gets the root schema definition.
     *
     * @return Returns the root schema.
     * @see Root schema
     */
    public Schema getRootSchema() {
        return rootSchema;
    }

    /**
     * Gets the "$id" keyword of the document.
     *
     * @return Returns the optionally defined $id.
     * @see $id
     */
    public Optional getIdKeyword() {
        return Optional.ofNullable(idKeyword);
    }

    /**
     * Gets the "$schema" keyword of the document.
     *
     * @return Returns the optionally defined $schema.
     * @see $schema
     */
    public Optional getSchemaKeyword() {
        return Optional.ofNullable(schemaKeyword);
    }

    /**
     * Gets a schema definition from the "definitions" map using a JSON pointer.
     *
     * 

The "root" schema is returned if {@code pointer} is an empty string. * * @param pointer JSON Schema pointer to retrieve. * @return Returns the optionally found schema definition. */ public Optional getDefinition(String pointer) { String unescaped = NodePointer.unescape(pointer); // Attempt to get the unescaped pointer, as-is. if (definitions.containsKey(unescaped)) { return Optional.ofNullable(definitions.get(unescaped)); } List pointerParts = NodePointer.parse(pointer).getParts(); // An empty pointer returns the root schema. if (pointerParts.isEmpty()) { return Optional.of(getRootSchema()); } // Compute the part of the pointer that points at a literal entry in // the map of definitions, and then compute the remaining pointer // segments that need to be used when retrieving a nested schema. String prefix = pointer.startsWith("#") ? "#" : ""; for (int position = 0; position < pointerParts.size(); position++) { String part = pointerParts.get(position); prefix += '/' + part; if (definitions.containsKey(prefix)) { List remaining = pointerParts.subList(position + 1, pointerParts.size()); String[] suffix = remaining.toArray(new String[0]); return definitions.get(prefix).selectSchema(suffix); } } return Optional.empty(); } /** * Gets all of the schema definitions defined in the "definitions" map. * * @return Returns the defined schema definitions. * @see Schema reuse with "definitions" */ public Map getDefinitions() { return definitions; } /** * Gets an extension value by name. * * @param key Name of the extension to retrieve. * @return Returns the extension object. */ public Optional getExtension(String key) { return extensions.getMember(key); } /** * Gets all extensions of the schema document. * * @return Returns the extensions added to the schema. */ public ObjectNode getExtensions() { return extensions; } @Override public boolean equals(Object o) { if (this == o) { return true; } else if (!(o instanceof SchemaDocument)) { return false; } SchemaDocument that = (SchemaDocument) o; return Objects.equals(idKeyword, that.idKeyword) && Objects.equals(schemaKeyword, that.schemaKeyword) && rootSchema.equals(that.rootSchema) && definitions.equals(that.definitions) && extensions.equals(that.extensions); } @Override public int hashCode() { return Objects.hash(idKeyword, schemaKeyword, rootSchema); } /** * Builds a JSON Schema document. */ public static final class Builder implements SmithyBuilder { private String idKeyword; private String schemaKeyword; private Schema rootSchema; private ObjectNode extensions = Node.objectNode(); private final Map definitions = new LinkedHashMap<>(); private Builder() {} @Override public SchemaDocument build() { return new SchemaDocument(this); } /** * Sets the "$id" keyword. * * @param idKeyword ID keyword URI to set. * @return Returns the builder. */ public Builder idKeyword(String idKeyword) { this.idKeyword = idKeyword; return this; } /** * Sets the "$schema" keyword. * * @param schemaKeyword Schema keyword URI to set. * @return Returns the builder. */ public Builder schemaKeyword(String schemaKeyword) { this.schemaKeyword = schemaKeyword; return this; } /** * Sets the root schema. * * @param rootSchema Root schema of the document. * @return Returns the builder. */ public Builder rootSchema(Schema rootSchema) { this.rootSchema = rootSchema; return this; } /** * Adds a scheme definition to the builder. * * @param name Name of the schema. * @param schema Schema to associate to the name. * @return Returns the builder. */ public Builder putDefinition(String name, Schema schema) { definitions.put(name, schema); return this; } /** * Removes a schema definition by name. * * @param name Name of the schema to remove. * @return Returns the builder. */ public Builder removeDefinition(String name) { definitions.remove(name); return this; } /** * Adds custom key-value pairs to the resulting JSON Schema document. * * @param extensions Extensions to apply. * @return Returns the builder. */ public Builder extensions(ObjectNode extensions) { this.extensions = extensions; return this; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy