
.. _program_listing_file_cif++_validate.hpp:

Program Listing for File validate.hpp
=====================================

|exhale_lsh| :ref:`Return to documentation for file <file_cif++_validate.hpp>` (``cif++/validate.hpp``)

.. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS

.. code-block:: cpp

   /*-
    * 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++/text.hpp"
   
   #include <cassert>
   #include <filesystem>
   #include <list>
   #include <mutex>
   #include <optional>
   #include <system_error>
   #include <utility>
   
   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, 
       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:
       const char *name() const noexcept override
       {
           return "cif::validation";
       }
   
       std::string message(int ev) const override
       {
           switch (static_cast<validation_error>(ev))
           {
               case validation_error::value_does_not_match_rx:
                   return "Value in item does not match regular expression";
               case validation_error::value_is_not_in_enumeration_list:
                   return "Value is not in the enumerated list of valid values";
               case validation_error::not_a_known_primitive_type:
                   return "The type is not a known primitive type";
               case validation_error::undefined_category:
                   return "Category has no definition in the dictionary";
               case validation_error::unknown_item:
                   return "Item is not defined to be part of the category";
               case validation_error::incorrect_item_validator:
                   return "Incorrectly specified validator for item";
               case validation_error::missing_mandatory_items:
                   return "Missing mandatory items";
               case validation_error::missing_key_items:
                   return "An index could not be constructed due to missing key items";
               case validation_error::item_not_allowed_in_category:
                   return "Requested item not allowed in category according to dictionary";
               case validation_error::empty_file:
                   return "The file contains no datablocks";
               case validation_error::empty_datablock:
                   return "The datablock contains no categories";
               case validation_error::empty_category:
                   return "The category is empty";
               case validation_error::not_valid_pdbx:
                   return "The file is not a valid PDBx file";
   
               default:
                   assert(false);
                   return "unknown error code";
           }
       }
   
       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 std::error_code(static_cast<int>(e), validation_category());
   }
   
   inline std::error_condition make_error_condition(validation_error e)
   {
       return std::error_condition(static_cast<int>(e), validation_category());
   }
   
   // --------------------------------------------------------------------
   
   class validation_exception : public std::runtime_error
   {
     public:
       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);
   
       type_validator(type_validator &&rhs)
       {
           swap(*this, rhs);
       }
   
       type_validator &operator=(type_validator rhs)
       {
           swap(*this, rhs);
           return *this;
       }
   
       ~type_validator();
   
       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;
       }
   
       int compare(std::string_view a, std::string_view b) const;
   };
   
   struct item_alias
   {
       item_alias(const std::string &alias_name, const std::string &dictionary, const std::string &version)
           : m_name(alias_name)
           , m_dict(dictionary)
           , m_vers(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;      
       cif::iset 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 operator()(std::string_view value) const;
   
       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::set<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);
   
       const item_validator *get_validator_for_item(std::string_view item_name) const;
   
       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);
   
       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);
   
       const type_validator *get_validator_for_type(std::string_view type_code) const;
   
       void add_category_validator(category_validator &&v);
   
       const category_validator *get_validator_for_category(std::string_view category) const;
   
       void add_link_validator(link_validator &&v);
   
       std::vector<const link_validator *> get_links_for_parent(std::string_view category) const;
   
       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_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;
   
       void fill_audit_conform(category &audit_conform) const;
   
       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:
       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);
   
       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));
       }
   
     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
