module DataModel # Registry allows for different type implementations to be used by the scanner. # It also acts as an error message registry, mostly for pragmatic reasons. class Registry # Default types that will be used if alternative type map is not given # @return [Hash] the default type map def self.default_types Builtin.types end # Default error messages that will be used if alternative error messages are not given # @return [Hash] the default error messages def self.default_error_messages Errors.error_messages end # Singleton instance that will be used globally unless instances given # @param types [Hash] the type map to use # @param errors [Hash] the error message map to use # @return [Registry] the singleton instance def self.instance(types: default_types, errors: default_error_messages) @instance ||= new(types:, errors:) end # Register a type on the global instance # @param name [Symbol] the name of the type # @param type [Type] the type to register # @return [void] def self.register(name, type) instance.register(name, type) end # Instanciate a new type registry. Default errors will always be used, but additional # errors can be registered. # @param types [Hash] the type map to use # @param errors [Hash] the error message map to use # @return [Registry] the new instance def initialize(types: self.class.default_types, errors: self.class.default_error_messages) @error_messages = nil if errors errors.each { |type, builder| register_error_message(type, &builder) } end @types = {} types.each { |(name, type)| register(name, type) } end # Register a type on this instance # @param name [Symbol] the name of the Type # @param type [Type] the type to register # @return [void] def register(name, type) @types[name] = type end # Check if a type is registered # @param name [Symbol] the name of the type # @return [Boolean] whether the type is registered def type?(name) @types.key?(name) end # Access and configure registered type # @param name [Symbol] the name of the Type # @param args [Hash] the arguments to pass to the Type # @param params [Array] the parameters to configure the Type with # @return [Type] the configured type def type(name, args: {}, params: nil) if !type?(name) raise "#{name} is not registered as a type" end t = @types.fetch(name).new(args, registry: self) if params t.configure(params) end return t end ## API # Register a custom error message for use with custom errors # @param type [Symbol] the type of error to register # @param block [Proc] the block to use to build the error message, shoudl take the error context and return a string # @return [void] def register_error_message(type, &block) error_message_builders[type] = block end # Get the error message builders # @return [Hash] the error message builders def error_message_builders if @error_messages.nil? @error_messages ||= {} end @error_messages end # Build the error message for a given error # @param error [Error] the error to build the message for # @return [String] the error message # @raise [RuntimeError] if no error message builder is registered for the error type def error_message(error) type = error[0] ctx = error[1] builder = error_message_builders[type] if builder.nil? raise "no error message builder for #{type}" end builder.call(ctx) end # Build error messages from error object # @param error [Error] the error to build the messages for # @return [Hash] the error messages def error_messages(error) error.to_h.transform_values do |error_list| error_list.map { |e| error_message(e) } end end end end