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

com.zuunr.jsonschema.ValueFormatConverter Maven / Gradle / Ivy

/*
 * Copyright 2020 Zuunr AB
 *
 * 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.zuunr.jsonschema;

import com.zuunr.forms.Form;
import com.zuunr.forms.FormField;
import com.zuunr.forms.FormFields;
import com.zuunr.forms.ValueFormat;
import com.zuunr.forms.formfield.Type;
import com.zuunr.json.JsonArray;
import com.zuunr.json.JsonObject;
import com.zuunr.json.JsonObjectBuilder;
import com.zuunr.json.JsonValue;

import java.util.Iterator;
import java.util.function.Function;

/**
 * @author Niklas Eldberger
 */
public class ValueFormatConverter {

    public static final JsonObject TYPE_MAPPING = JsonObject.EMPTY.builder()
            .put(Type.STRING.toString(), JsonObject.EMPTY.put("type", JsonArray.of("string")))
            .put(Type.OBJECT.toString(), JsonObject.EMPTY.put("type", JsonArray.of("object")))
            .put(Type.ARRAY.toString(), JsonObject.EMPTY.put("type", JsonArray.of("array")))
            .put(Type.BOOLEAN.toString(), JsonObject.EMPTY.put("type", JsonArray.of("boolean")))
            .put(Type.SET.toString(), JsonObject.EMPTY.put("type", JsonArray.of("array")))
            .put(Type.DATE.toString(), JsonObject.EMPTY.put("type", JsonArray.of("date")))
            .put(Type.DATETIME.toString(), JsonObject.EMPTY.put("type", JsonArray.of("dateTime")))
            .put(Type.INTEGER.toString(), JsonObject.EMPTY.put("type", JsonArray.of("integer")))
            .put(Type.UNDEFINED.toString(), JsonObject.EMPTY)
            .build();

    private static final JsonObject NULLABLE_TYPE_MAPPING = JsonObject.EMPTY.builder()
            .put(Type.STRING.toString(), JsonObject.EMPTY.put("type", JsonArray.of("string", "null")))
            .put(Type.OBJECT.toString(), JsonObject.EMPTY.put("type", JsonArray.of("object", "null")))
            .put(Type.ARRAY.toString(), JsonObject.EMPTY.put("type", JsonArray.of("array", "null")))
            .put(Type.BOOLEAN.toString(), JsonObject.EMPTY.put("type", JsonArray.of("boolean", "null")))
            .put(Type.SET.toString(), JsonObject.EMPTY.put("type", JsonArray.of("array", "null")))
            .put(Type.DATE.toString(), JsonObject.EMPTY.put("type", JsonArray.of("date", "null")))
            .put(Type.DATETIME.toString(), JsonObject.EMPTY.put("type", JsonArray.of("dateTime", "null")))
            .put(Type.INTEGER.toString(), JsonObject.EMPTY.put("type", JsonArray.of("integer", "null")))
            .put(Type.UNDEFINED.toString(), JsonObject.EMPTY)
            .build();

    // VALUE_FORMAT_VALIDATION_KEY_MAPPINGS - when value has same meaning and format in JsonSchema and ValueFormat
    private static final JsonObject VALUE_FORMAT_VALIDATION_KEY_MAPPINGS = JsonObject.EMPTY.builder()
            .put("max", "maximum")
            .put("exclusiveMaximum", "exclusiveMaximum")
            .put("exclusiveMinimum", "exclusiveMinimum")
            .put("maxsize", "maxItems")
            .put("maxlength", "maxLength")
            .put("min", "minimum")
            .put("minsize", "minItems")
            .put("minlength", "minLength")
            .put("pattern", "pattern")
            .put("desc", "description")
            .put("enum", "enum")
            .put("const", "const")
            .put("typeFormat", "format")
            .build();

    private JsonObject convertTypeAndNullable(ValueFormat valueFormat) {
        ValueFormat explicitValueFormat = valueFormat.asExplicitValueFormat();
        JsonObject mapping = explicitValueFormat.nullable().booleanValue() ? NULLABLE_TYPE_MAPPING : TYPE_MAPPING;
        JsonObject jsonObject = mapping.get(explicitValueFormat.type().toString(), JsonValue.NULL).getValue(JsonObject.class);
        if (jsonObject == null) {
            throw new ConversionException("Type not supported: " + valueFormat.type());
        }
        return jsonObject;
    }

    private Function schemaStyleFormatter;

    public ValueFormatConverter(Function schemaStyleFormatter) {
        this.schemaStyleFormatter = schemaStyleFormatter;
    }

    public ValueFormatConverter() {
        schemaStyleFormatter = valueFormatAndSchemaTuple -> valueFormatAndSchemaTuple.jsonSchema;
    }

    public JsonObject translate(ValueFormat valueFormat) {
        ValueFormat explicitValueFormat = valueFormat.asExplicitValueFormat();
        JsonObjectBuilder jsonSchemaBuilder = convertTypeAndNullable(valueFormat).builder();

        translateSimpleMappings(valueFormat, jsonSchemaBuilder);
        translateOpenApiStyle(valueFormat, jsonSchemaBuilder);

        if (explicitValueFormat.type().isStringTypeOrSubtypeOfString()) {
            translateStringType(valueFormat, jsonSchemaBuilder);
        } else if (explicitValueFormat.type().isObject()) {
            translateObjectType(valueFormat, jsonSchemaBuilder);
        } else if (explicitValueFormat.type().isArrayOrSet()) {
            translateArrayType(valueFormat, jsonSchemaBuilder);
        }
        return schemaStyleFormatter.apply(new ValueFormatAndSchemaTuple(valueFormat, jsonSchemaBuilder.build()));
    }

    private void translateOpenApiStyle(ValueFormat valueFormat, JsonObjectBuilder jsonSchemaBuilder) {
        if (valueFormat.asExplicitValueFormat().constant() != null){
            jsonSchemaBuilder.put("enum", JsonArray.of(valueFormat.constant()));
            jsonSchemaBuilder.remove("const");
        }
        if (valueFormat.asExplicitValueFormat().type().isStringTypeOrSubtypeOfString()) {
            jsonSchemaBuilder.remove("maximum");
            jsonSchemaBuilder.remove("minimum");
        }
    }

    private void translateSimpleMappings(ValueFormat valueFormat, JsonObjectBuilder jsonSchemaBuilder) {

        JsonObject compactValueFormat = valueFormat.asCompactValueFormat().asJsonValue().getJsonObject();
        JsonArray keys = compactValueFormat.keys();
        Iterator values = compactValueFormat.values().iterator();
        for (String key : keys.asList(String.class)) {
            JsonValue value = values.next();
            JsonValue mappedKeyword = VALUE_FORMAT_VALIDATION_KEY_MAPPINGS.get(key);
            if (mappedKeyword != null) {
                jsonSchemaBuilder.put(mappedKeyword.getString(), value);
            }
        }
    }

    private void translateStringType(ValueFormat valueFormat, JsonObjectBuilder jsonSchemaBuilder) {
        // subtypes of strings should be handled here
    }


    private void translateObjectType(ValueFormat valueFormat, JsonObjectBuilder jsonSchemaBuilder) {
        translateExclusive(valueFormat, jsonSchemaBuilder);
        translateRequired(valueFormat, jsonSchemaBuilder);
        translateForm(valueFormat, jsonSchemaBuilder);
    }

    private void translateArrayType(ValueFormat valueFormat, JsonObjectBuilder jsonSchemaBuilder) {
        JsonObject itemsSchema = translate(valueFormat.asExplicitValueFormat().element());
        jsonSchemaBuilder.put("items", itemsSchema);
    }

    private void translateForm(ValueFormat valueFormat, JsonObjectBuilder jsonSchemaBuilder) {
        JsonObjectBuilder propertiesBuilder = JsonObject.EMPTY.builder();
        Form form = valueFormat.form();
        if (form != null) {
            FormFields formFields = form.formFields();
            for (FormField formField : formFields.asList()) {
                propertiesBuilder.put(formField.name(), translate(formField.asValueFormat()));
            }
        }
        jsonSchemaBuilder.put("properties", propertiesBuilder.build());
    }

    private void translateExclusive(ValueFormat valueFormat, JsonObjectBuilder jsonSchemaBuilder) {
        Form form = valueFormat.form();
        if (form == null) {
            jsonSchemaBuilder.put("additionalProperties", true);
        } else if (form.exclusive() == null) {
            jsonSchemaBuilder.remove("additionalProperties");
        } else {
            jsonSchemaBuilder.put("additionalProperties", !form.exclusive());
        }
    }

    private void translateRequired(ValueFormat valueFormat, JsonObjectBuilder jsonSchemaBuilder) {
        if (valueFormat.form() != null) {

            JsonArray required = valueFormat.form().requiredFormFields().keys().sort();
            if (!required.isEmpty()) {
                jsonSchemaBuilder.put("required", required);
            }
        }
    }

    public class ValueFormatAndSchemaTuple {
        public final ValueFormat valueFormat;
        public final JsonObject jsonSchema;

        public ValueFormatAndSchemaTuple(ValueFormat valueFormat, JsonObject jsonSchema) {
            this.valueFormat = valueFormat;
            this.jsonSchema = jsonSchema;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy