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

io.helidon.microprofile.graphql.server.CustomScalars Maven / Gradle / Ivy

There is a newer version: 4.1.4
Show newest version
/*
 * Copyright (c) 2020, 2022 Oracle and/or its affiliates.
 *
 * 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 io.helidon.microprofile.graphql.server;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Date;

import graphql.language.StringValue;
import graphql.scalars.ExtendedScalars;
import graphql.schema.Coercing;
import graphql.schema.CoercingParseLiteralException;
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import graphql.schema.GraphQLScalarType;

import static graphql.Scalars.GraphQLFloat;
import static graphql.Scalars.GraphQLInt;
import static graphql.scalars.ExtendedScalars.GraphQLBigDecimal;
import static graphql.scalars.ExtendedScalars.GraphQLBigInteger;
import static io.helidon.microprofile.graphql.server.SchemaGeneratorHelper.DATETIME_SCALAR;
import static io.helidon.microprofile.graphql.server.SchemaGeneratorHelper.DATE_SCALAR;
import static io.helidon.microprofile.graphql.server.SchemaGeneratorHelper.FORMATTED_DATETIME_SCALAR;
import static io.helidon.microprofile.graphql.server.SchemaGeneratorHelper.FORMATTED_DATE_SCALAR;
import static io.helidon.microprofile.graphql.server.SchemaGeneratorHelper.FORMATTED_OFFSET_DATETIME_SCALAR;
import static io.helidon.microprofile.graphql.server.SchemaGeneratorHelper.FORMATTED_TIME_SCALAR;
import static io.helidon.microprofile.graphql.server.SchemaGeneratorHelper.FORMATTED_ZONED_DATETIME_SCALAR;
import static io.helidon.microprofile.graphql.server.SchemaGeneratorHelper.TIME_SCALAR;

/**
 * Custom scalars.
 */
class CustomScalars {

    /**
     * Private no-args constructor.
     */
    private CustomScalars() {
    }

    /**
     * An instance of a custome BigDecimal Scalar.
     */
    static final GraphQLScalarType CUSTOM_BIGDECIMAL_SCALAR = newCustomBigDecimalScalar();

    /**
     * An instance of a custom Int scalar.
     */
    static final GraphQLScalarType CUSTOM_INT_SCALAR = newCustomGraphQLInt();

    /**
     * An instance of a custom Float scalar.
     */
    static final GraphQLScalarType CUSTOM_FLOAT_SCALAR = newCustomGraphQLFloat();

    /**
     * An instance of a custom BigInteger scalar.
     */
    static final GraphQLScalarType CUSTOM_BIGINTEGER_SCALAR = newCustomGraphQLBigInteger();

    /**
     * An instance of a custom formatted date/time scalar.
     */
    static final GraphQLScalarType FORMATTED_CUSTOM_DATE_TIME_SCALAR = newDateTimeScalar(FORMATTED_DATETIME_SCALAR);

    /**
     * An instance of a custom formatted time scalar.
     */
     static final GraphQLScalarType FORMATTED_CUSTOM_TIME_SCALAR = newTimeScalar(FORMATTED_TIME_SCALAR);

    /**
     * An instance of a custom formatted date scalar.
     */
    static final GraphQLScalarType FORMATTED_CUSTOM_DATE_SCALAR = newDateScalar(FORMATTED_DATE_SCALAR);

    /**
     * An instance of a custom date/time scalar (with default formatting).
     */
    static final GraphQLScalarType CUSTOM_DATE_TIME_SCALAR = newDateTimeScalar(DATETIME_SCALAR);

    /**
     * An instance of a custom offset date/time scalar (with default formatting).
     */
     static final GraphQLScalarType CUSTOM_OFFSET_DATE_TIME_SCALAR =
            newOffsetDateTimeScalar(FORMATTED_OFFSET_DATETIME_SCALAR);

    /**
     * An instance of a custom offset date/time scalar (with default formatting).
     */
     static final GraphQLScalarType CUSTOM_ZONED_DATE_TIME_SCALAR =
            newZonedDateTimeScalar(FORMATTED_ZONED_DATETIME_SCALAR);

    /**
     * An instance of a custom time scalar (with default formatting).
     */
     static final GraphQLScalarType CUSTOM_TIME_SCALAR = newTimeScalar(TIME_SCALAR);

    /**
     * An instance of a custom date scalar (with default formatting).
     */
     static final GraphQLScalarType CUSTOM_DATE_SCALAR = newDateScalar(DATE_SCALAR);

    /**
     * Return a new custom date/time scalar.
     *
     * @param name the name of the scalar
     * @return a new custom date/time scalar
     */
     static GraphQLScalarType newDateTimeScalar(String name) {
        GraphQLScalarType originalScalar = ExtendedScalars.DateTime;

        return GraphQLScalarType.newScalar()
                .coercing(new DateTimeCoercing())
                .name(name)
                .description("Custom: " + originalScalar.getDescription())
                .build();
    }

    /**
     * Return a new custom offset date/time scalar.
     *
     * @param name the name of the scalar
     * @return a new custom date/time scalar
     */
    @SuppressWarnings("unchecked")
     static GraphQLScalarType newOffsetDateTimeScalar(String name) {
        GraphQLScalarType originalScalar = ExtendedScalars.DateTime;

        return GraphQLScalarType.newScalar()
                .coercing(new DateTimeCoercing())
                .name(name)
                .description("Custom: " + originalScalar.getDescription())
                .build();
    }

    /**
     * Return a new custom zoned date/time scalar.
     *
     * @param name the name of the scalar
     * @return a new custom date/time scalar
     */
    @SuppressWarnings("unchecked")
     static GraphQLScalarType newZonedDateTimeScalar(String name) {
        GraphQLScalarType originalScalar = ExtendedScalars.DateTime;

        return GraphQLScalarType.newScalar()
                .coercing(new DateTimeCoercing())
                .name(name)
                .description("Custom: " + originalScalar.getDescription())
                .build();
    }

    /**
     * Return a new custom time scalar.
     *
     * @param name the name of the scalar
     * @return a new custom time scalar
     */
     static GraphQLScalarType newTimeScalar(String name) {
        GraphQLScalarType originalScalar = ExtendedScalars.Time;

        return GraphQLScalarType.newScalar()
                .coercing(new TimeCoercing())
                .name(name)
                .description("Custom: " + originalScalar.getDescription())
                .build();
    }

    /**
     * Return a new custom date scalar.
     *
     * @param name the name of the scalar
     * @return a new custom date scalar
     */
     static GraphQLScalarType newDateScalar(String name) {
        GraphQLScalarType originalScalar = ExtendedScalars.Date;
        return GraphQLScalarType.newScalar()
                .coercing(new DateCoercing())
                .name(name)
                .description("Custom: " + originalScalar.getDescription())
                .build();
    }

    /**
     * Return a new custom BigDecimal scalar.
     *
     * @return a new custom BigDecimal scalar
     */
    private static GraphQLScalarType newCustomBigDecimalScalar() {
        GraphQLScalarType originalScalar = GraphQLBigDecimal;

        return GraphQLScalarType.newScalar()
                .coercing(new NumberCoercing(originalScalar.getCoercing()))
                .name(originalScalar.getName())
                .description("Custom: " + originalScalar.getDescription())
                .build();
    }

    /**
     * Return a new custom Int scalar.
     *
     * @return a new custom Int scalar
     */
    private static GraphQLScalarType newCustomGraphQLInt() {
        GraphQLScalarType originalScalar = GraphQLInt;

        return GraphQLScalarType.newScalar()
                .coercing(new NumberCoercing(originalScalar.getCoercing()))
                .name(originalScalar.getName())
                .description("Custom: " + originalScalar.getDescription())
                .build();
    }

    /**
     * Return a new custom Float scalar.
     *
     * @return a new custom Float scalar
     */
    private static GraphQLScalarType newCustomGraphQLFloat() {
        GraphQLScalarType originalScalar = GraphQLFloat;

        return GraphQLScalarType.newScalar()
                .coercing(new NumberCoercing(originalScalar.getCoercing()))
                .name(originalScalar.getName())
                .description("Custom: " + originalScalar.getDescription())
                .build();
    }

    /**
     * Return a new custom BigInteger scalar.
     *
     * @return a new custom BigInteger scalar
     */
    private static GraphQLScalarType newCustomGraphQLBigInteger() {
        GraphQLScalarType originalScalar = GraphQLBigInteger;

        return GraphQLScalarType.newScalar()
                .coercing(new NumberCoercing(originalScalar.getCoercing()))
                .name(originalScalar.getName())
                .description("Custom: " + originalScalar.getDescription())
                .build();
    }

    /**
     * Abstract implementation of {@link Coercing} interface for given classes.
     */
     abstract static class AbstractDateTimeCoercing implements Coercing {

        /**
         * {@link Class}es that can be coerced.
         */
        private final Class[] clazzes;

        /**
         * Construct a {@link AbstractDateTimeCoercing}.
         *
         * @param clazzes {@link Class}es to coerce
         */
         AbstractDateTimeCoercing(Class... clazzes) {
            this.clazzes = clazzes;
        }

        @Override
        public Object serialize(Object dataFetcherResult) throws CoercingSerializeException {
            return convert(dataFetcherResult);
        }

        @Override
        public Object parseValue(Object input) throws CoercingParseValueException {
            return convert(input);
        }

        @Override
        public Object parseLiteral(Object input) throws CoercingParseLiteralException {
            return parseStringLiteral(input);
        }

        /**
         * Convert the given input to the type of if a String then leave it be.
         *
         * @param input input to coerce
         * @return the coerced value
         * @throws CoercingParseLiteralException if any exceptions converting
         */
        private Object convert(Object input) throws CoercingParseLiteralException {
            if (input instanceof String) {
                return input;
            }

            for (Class clazz : clazzes) {
                if (clazz.isInstance(input)) {
                    return input;
                }
            }

            throw new CoercingParseLiteralException("Unable to convert type of " + input.getClass().toString()
                                                    + " with classes " + Arrays.toString(clazzes));
        }

        /**
         * Parse a String literal and return instance of {@link StringValue} or throw an exception.
         *
         * @param input input to parse
         * @throws CoercingParseLiteralException if it is not a {@link StringValue}
         */
        private String parseStringLiteral(Object input) throws CoercingParseLiteralException {
            if (!(input instanceof StringValue)) {
                throw new CoercingParseLiteralException("Expected AST type 'StringValue' but was '"
                                                                + (
                        input == null
                                ? "null"
                                : input.getClass().getSimpleName()) + "'.");
            }
            return ((StringValue) input).getValue();
        }
    }

    /**
     * Coercing Implementation for Date/Time.
     */
     static class DateTimeCoercing extends AbstractDateTimeCoercing {

        /**
         * Construct a {@link DateTimeCoercing}.
         */
         DateTimeCoercing() {
            super(LocalDateTime.class, OffsetDateTime.class, ZonedDateTime.class);
        }
    }

    /**
     * Coercing implementation for Time.
     */
     static class TimeCoercing extends AbstractDateTimeCoercing {

        /**
         * Construct a {@link TimeCoercing}.
         */
         TimeCoercing() {
            super(LocalTime.class, OffsetTime.class);
        }
    }

    /**
     * Coercing implementation for Date.
     */
     static class DateCoercing extends AbstractDateTimeCoercing {

        /**
         * Construct a {@link DateCoercing}.
         */
         DateCoercing() {
            super(LocalDate.class, Date.class);
        }
    }

    /**
     * Coercing implementation for BigDecimal.
     */
     static class BigDecimalCoercing extends AbstractDateTimeCoercing {

        /**
         * Construct a {@link BigDecimalCoercing}.
         */
         BigDecimalCoercing() {
            super(BigDecimal.class);
        }
    }

    /**
     * Number implementation of {@link Coercing} interface for given classes.
     * @param  defines input type
     */
    @SuppressWarnings("unchecked")
     static class NumberCoercing implements Coercing {

        /**
         * Original {@link Coercing} to fall back on if neeed.
         */
        private final Coercing originalCoercing;

        /**
         * Construct a {@link NumberCoercing} from an original {@link Coercing}.
         *
         * @param originalCoercing original {@link Coercing}
         */
         NumberCoercing(Coercing originalCoercing) {
            this.originalCoercing = originalCoercing;
        }

        @Override
        public Object serialize(Object dataFetcherResult) throws CoercingSerializeException {
            return dataFetcherResult instanceof String
                    ? (String) dataFetcherResult
                    : originalCoercing.serialize(dataFetcherResult);
        }

        @Override
        public I parseValue(Object input) throws CoercingParseValueException {
            return (I) originalCoercing.parseValue(input);
        }

        @Override
        public I parseLiteral(Object input) throws CoercingParseLiteralException {
            return (I) originalCoercing.parseLiteral(input);
        }
    }
}