module DataModel # @abstract Base class for all types. # Types have arguments, which configure the act of reading / validating / coercing data. # They also have parameters, which configure the type itself, such as generic or child specification # Arguments are passed to the type when it is invoked, and parameters are passed to the type when it is configured. # Parameters are really only used in complex types, such as Array or Hash. If you don't need them, you can ignore them. class Type # @param args [Hash] type arguments, configures the reading process # @param registry [Registry] the registry to use # @return [void] def initialize(args, registry: Registry.instance) @type_args = args @type_registry = registry end # @return [Hash] the type arguments attr_reader :type_args # @return [Registry] the type Registry attr_reader :type_registry # configure must be overridden to use params. If you don't need params, you can ignore this. # @param params [Array] type parameters, configures the type itself # @return [void] def configure(params); end # invoke another type by name. This is useful for specifying types like UUIDs which are specialized strings # @param name [Symbol] the name of the type to invoke # @param val [Object] the value to read # @param coerce [Boolean] whether to coerce the value # @param args [Hash] type arguments, configures the reading process # @param params [Array] type parameters, configures the type itself # @return [Array(Object, Error)] the result of reading the value def invoke(name, val, coerce: false, args: {}, params: nil) t = instantiate(name, args:, params:) result = t.read(val, coerce:) return result end # instanciate another type by name # @param name [Symbol] the name of the type to instantiate # @param args [Hash] type arguments, configures the reading Process # @param params [Array] type parameters, configures the type itself # @return [Type] the instantiated type def instantiate(name, args: {}, params: nil) t = @type_registry.type(name, args:, params:) return t end # @abstract default reader, must be overridden for a type to be useful # @param data [untyped] the data to read # @param coerce [Boolean] whether to coerce the value # @return [Array(Object, Error)] the result of reading the value def read(data, coerce: false); end # name of the type without module prefix as a string # useful for generating error messages # @return [String] the type name def type_name @type_name ||= self.class.name.split("::").last end end end