# Program Listing for File validate.hpp

↰ Return to documentation for file (`cif++/validate.hpp`)

    /*-
     * SPDX-License-Identifier: BSD-2-Clause
     *
     * Copyright (c) 2022 NKI/AVL, Netherlands Cancer Institute
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer
     * 2. Redistributions in binary form must reproduce the above copyright notice,
     *    this list of conditions and the following disclaimer in the documentation
     *    and/or other materials provided with the distribution.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
     * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
     * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     */

    #pragma once

    #include "cif++/category.hpp"
    #include "cif++/item.hpp"
    #include "cif++/text.hpp"

    #include <cassert>
    #include <iosfwd>
    #include <list>
    #include <memory>
    #include <mutex>
    #include <optional>
    #include <set>
    #include <stdexcept>
    #include <string>
    #include <string_view>
    #include <system_error>
    #include <utility>
    #include <vector>

    namespace cif
    {

    class category;
    struct category_validator;

    // --------------------------------------------------------------------
    // New: error_code

    enum class validation_error
    {
        value_does_not_match_rx = 1,
        value_is_not_in_enumeration_list,
        value_is_not_a_number,
        value_is_not_a_char_string,
        not_a_known_primitive_type,
        undefined_category,
        unknown_item,
        incorrect_item_validator,
        missing_mandatory_items,
        missing_key_items,
        item_not_allowed_in_category,
        empty_file,
        empty_datablock,
        empty_category,
        not_valid_pdbx,
    };
    class validation_category_impl : public std::error_category
    {
      public:

        [[nodiscard]] const char *name() const noexcept override
        {
            return "cif::validation";
        }

        [[nodiscard]] std::string message(int ev) const override
        {
            switch (static_cast<validation_error>(ev))
            {
                using enum validation_error;

                case value_does_not_match_rx:
                    return "Value in item does not match regular expression";
                case value_is_not_in_enumeration_list:
                    return "Value is not in the enumerated list of valid values";
                case value_is_not_a_number:
                    return "Value is not a number";
                case value_is_not_a_char_string:
                    return "Value is not a character string";
                case not_a_known_primitive_type:
                    return "The type is not a known primitive type";
                case undefined_category:
                    return "Category has no definition in the dictionary";
                case unknown_item:
                    return "Item is not defined to be part of the category";
                case incorrect_item_validator:
                    return "Incorrectly specified validator for item";
                case missing_mandatory_items:
                    return "Missing mandatory items";
                case missing_key_items:
                    return "An index could not be constructed due to missing key items";
                case item_not_allowed_in_category:
                    return "Requested item not allowed in category according to dictionary";
                case empty_file:
                    return "The file contains no datablocks";
                case empty_datablock:
                    return "The datablock contains no categories";
                case empty_category:
                    return "The category is empty";
                case not_valid_pdbx:
                    return "The file is not a valid PDBx file";

                default:
                    assert(false);
                    return "unknown error code";
            }
        }

        [[nodiscard]] bool equivalent(const std::error_code & /*code*/, int /*condition*/) const noexcept override
        {
            return false;
        }
    };

    inline std::error_category &validation_category()
    {
        static validation_category_impl instance;
        return instance;
    }

    inline std::error_code make_error_code(validation_error e)
    {
        return { static_cast<int>(e), validation_category() };
    }

    inline std::error_condition make_error_condition(validation_error e)
    {
        return { static_cast<int>(e), validation_category() };
    }

    // --------------------------------------------------------------------

    class validation_exception : public std::runtime_error
    {
      public:
        // Constructors

        validation_exception(validation_error err)
            : validation_exception(make_error_code(err))
        {
        }

        validation_exception(validation_error err, std::string_view category)
            : validation_exception(make_error_code(err), category)
        {
        }

        validation_exception(validation_error err, std::string_view category, std::string_view item)
            : validation_exception(make_error_code(err), category, item)
        {
        }

        validation_exception(std::error_code ec);

        validation_exception(std::error_code ec, std::string_view category);

        validation_exception(std::error_code ec, std::string_view category, std::string_view item);
    };

    // --------------------------------------------------------------------

    enum class DDL_PrimitiveType
    {
        Char,
        UChar,
        Numb
    };

    DDL_PrimitiveType map_to_primitive_type(std::string_view s);

    DDL_PrimitiveType map_to_primitive_type(std::string_view s, std::error_code &ec) noexcept;

    struct regex_impl;

    struct type_validator
    {
        std::string m_name;
        DDL_PrimitiveType m_primitive_type;
        std::shared_ptr<regex_impl> m_rx;

        type_validator() = delete;

        type_validator(std::string_view name, DDL_PrimitiveType type, std::string_view rx);

        type_validator(const type_validator &tv) = default;

        type_validator(type_validator &&rhs)
        {
            swap(*this, rhs);
        }

        type_validator &operator=(type_validator rhs)
        {
            swap(*this, rhs);
            return *this;
        }

        ~type_validator() = default;

        friend void swap(type_validator &a, type_validator &b)
        {
            std::swap(a.m_name, b.m_name);
            std::swap(a.m_primitive_type, b.m_primitive_type);
            std::swap(a.m_rx, b.m_rx);
        }

        bool operator<(const type_validator &rhs) const
        {
            return icompare(m_name, rhs.m_name) < 0;
        }

        [[nodiscard]] int compare(const item_value &a, const item_value &b) const;
    };

    struct item_alias
    {
        item_alias(std::string alias_name, std::string dictionary, std::string version)
            : m_name(std::move(alias_name))
            , m_dict(std::move(dictionary))
            , m_vers(std::move(version))
        {
        }

        item_alias(const item_alias &) = default;

        item_alias &operator=(const item_alias &) = default;

        std::string m_name;
        std::string m_dict;
        std::string m_vers;
    };

    struct item_validator
    {
        std::string m_item_name;
        bool m_mandatory;
        const type_validator *m_type;
        std::set<std::string> m_enums;
        std::string m_default;
        std::string m_category;
        std::vector<item_alias> m_aliases;

        bool operator<(const item_validator &rhs) const
        {
            return icompare(m_item_name, rhs.m_item_name) < 0;
        }

        bool operator==(const item_validator &rhs) const
        {
            return iequals(m_item_name, rhs.m_item_name);
        }

        void validate_value(const item_value &value) const;

        bool validate_value(const item_value &value, std::error_code &ec) const noexcept;

        bool validate_value(std::string_view value, std::error_code &ec) const noexcept;
    };

    struct category_validator
    {
        std::string m_name;
        std::vector<std::string> m_keys;
        cif::iset m_groups;
        cif::iset m_mandatory_items;
        std::vector<item_validator> m_item_validators;

        bool operator<(const category_validator &rhs) const
        {
            return icompare(m_name, rhs.m_name) < 0;
        }

        void add_item_validator(item_validator &&v);

        [[nodiscard]] const item_validator *get_validator_for_item(std::string_view item_name) const;

        [[nodiscard]] const item_validator *get_validator_for_aliased_item(std::string_view item_name) const;
    };

    struct link_validator
    {
        int m_link_group_id;
        std::string m_parent_category;
        std::vector<std::string> m_parent_keys;
        std::string m_child_category;
        std::vector<std::string> m_child_keys;
        std::string m_link_group_label;
    };

    // --------------------------------------------------------------------

    class validator
    {
      public:
        validator()
            : m_audit_conform("audit_conform")
        {
        }

        validator(std::istream &is)
            : m_audit_conform("audit_conform")
        {
            parse(is);
        }

        ~validator() = default;

        validator(const validator &rhs) = default;

        validator(validator &&rhs)
        {
            swap(*this, rhs);
        }

        validator &operator=(validator rhs)
        {
            swap(*this, rhs);
            return *this;
        }

        friend void swap(validator &a, validator &b) noexcept;

        friend class dictionary_parser;
        friend class validator_factory;

        void parse(std::istream &is);

        void add_type_validator(type_validator &&v);

        [[nodiscard]] const type_validator *get_validator_for_type(std::string_view type_code) const;

        void add_category_validator(category_validator &&v);

        [[nodiscard]] const category_validator *get_validator_for_category(std::string_view category) const;

        void add_link_validator(link_validator &&v);

        [[nodiscard]] std::vector<const link_validator *> get_links_for_parent(std::string_view category) const;

        [[nodiscard]] std::vector<const link_validator *> get_links_for_child(std::string_view category) const;

        void report_error(validation_error err, bool fatal = true) const
        {
            report_error(make_error_code(err), fatal);
        }

        void report_error(std::error_code ec, bool fatal = true) const;

        void report_error(validation_error err, std::string value, std::string_view category,
            std::string_view item, bool fatal = true) const
        {
            report_error(make_error_code(err), value, category, item, fatal);
        }

        void report_error(validation_error err, std::string_view category,
            std::string_view item, bool fatal = true) const
        {
            report_error(make_error_code(err), "", category, item, fatal);
        }

        void report_error(std::error_code ec, std::string_view category,
            std::string_view item, bool fatal = true) const
        {
            report_error(ec, "", category, item, fatal);
        }

        void report_error(std::error_code ec, std::string value, std::string_view category,
            std::string_view item, bool fatal = true) const;

        void fill_audit_conform(category &audit_conform) const;

        [[nodiscard]] bool matches_audit_conform(const category &audit_conform) const;

        void append_audit_conform(const std::string &name, const std::optional<std::string> &version);

      private:
        // name is fully qualified here:
        [[nodiscard]] item_validator *get_validator_for_item(std::string_view name) const;

        category m_audit_conform;

        bool m_strict = false;
        std::set<type_validator> m_type_validators;
        std::set<category_validator> m_category_validators;
        std::vector<link_validator> m_link_validators;
    };

    // --------------------------------------------------------------------

    class validator_factory
    {
      public:
        static validator_factory &instance();

        const validator *get(const category &audit_conform);

        const validator *get(std::string_view dictionary_name);

        const validator &operator[](const category &audit_conform);

        const validator &operator[](std::string_view dictionary_name);

        static bool check_version(std::string_view name, std::string_view expected, std::string_view found);

        const validator &add(validator &&v)
        {
            std::unique_lock lock(m_mutex);
            return m_validators.emplace_back(std::move(v));
        }

        // #if __cplusplus >= 202302L
        //  /// @brief Return validator with info recorded in @a audit_conform
        //  static validator &operator[](const category &audit_conform)
        //  {
        //      return instance()[audit_conform];
        //  }

        //  /// @brief Return the single-file validator with name @a dictionary_name
        //  /// and the dictionary name may be a set of dictionaries separated by comma
        //  static validator &operator[](std::string_view dict)
        //  {
        //      return instance()[dict];
        //  }
        // #endif

      private:
        validator_factory() = default;

        validator construct_validator(std::string_view name, std::optional<std::string> version);

        std::mutex m_mutex;
        std::list<validator> m_validators;
    };

    } // namespace cif