Class: Lutaml::Model::Type::Value

Inherits:
Object
  • Object
show all
Includes:
UninitializedClassGuard, Xml::Type::Configurable
Defined in:
lib/lutaml/model/type/value.rb

Overview

Base class for all value types

Constant Summary collapse

EMPTY_OPTIONS =

Performance optimization: reusable empty options hash Use options.equal?(EMPTY_OPTIONS) for fast-path checks

{}.freeze
MIN_FACETS =

Canonical facet keys grouped by how a tighter constraint compares: a min-like facet tightens upward (greater wins), a max-like facet tightens downward (lesser wins). :length (exact) only matches equal.

%i[min_inclusive min_exclusive min_length].freeze
MAX_FACETS =
%i[
  max_inclusive
  max_exclusive
  max_length
  total_digits
  fraction_digits
].freeze
LIST_FACETS =

Facets whose combined value is the concatenation of every declaration in the chain: accumulating them is always a tightening (all patterns must match), so they bypass the widen check on inheritance.

%i[pattern].freeze
WHITE_SPACE_MODES =

xs:whiteSpace normalization modes in ascending strictness: a stricter mode transforms at least as much as a looser one, so tighter_facet picks the stricter and a subclass may tighten but not loosen it.

%i[preserve replace collapse].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Xml::Type::Configurable

included

Methods included from UninitializedClassGuard

#cast, #serialize

Constructor Details

#initialize(value) ⇒ Value

Returns a new instance of Value.



304
305
306
# File 'lib/lutaml/model/type/value.rb', line 304

def initialize(value)
  @value = self.class.cast(value)
end

Instance Attribute Details

#valueObject (readonly)

Returns the value of attribute value.



302
303
304
# File 'lib/lutaml/model/type/value.rb', line 302

def value
  @value
end

Class Method Details

.cast(value, _options = {}) ⇒ Object



312
313
314
315
316
317
# File 'lib/lutaml/model/type/value.rb', line 312

def self.cast(value, _options = {})
  return nil if value.nil?
  return value if Utils.uninitialized?(value)

  value
end

.enumeration(*values) ⇒ Object

Restrict to an enumerated set of allowed values (any type). A subclass narrows the parent's set; the effective set is the intersection across the chain (see tighter_facet).



156
157
158
# File 'lib/lutaml/model/type/value.rb', line 156

def enumeration(*values)
  declare_facets(enumeration: values.map { |v| cast_facet_value(v) })
end

.exclusive(min: nil, max: nil) ⇒ Object

Constrain ordered values via min/maxExclusive.



136
137
138
139
140
141
# File 'lib/lutaml/model/type/value.rb', line 136

def exclusive(min: nil, max: nil)
  min = cast_facet_value(min)
  max = cast_facet_value(max)
  raise_unordered!("exclusive", min, max)
  declare_facets(min_exclusive: min, max_exclusive: max)
end

.facetsObject

Effective canonical facets for this type, merged parent-first. A subclass may only tighten an inherited facet; widening raises.



101
102
103
104
105
106
107
# File 'lib/lutaml/model/type/value.rb', line 101

def facets
  facet_layers.each_with_object({}) do |layer, merged|
    layer.each do |key, value|
      merged[key] = inherit_facet(key, merged[key], value)
    end
  end
end

.format_type_serializer_for(format, type_class) ⇒ Hash?

Look up a format type serializer, walking the class hierarchy.

Parameters:

  • format (Symbol)

    the format

  • type_class (Class)

    the type class to look up

Returns:

  • (Hash, nil)

    { to: Proc, from: Proc } or nil



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/lutaml/model/type/value.rb', line 69

def format_type_serializer_for(format, type_class)
  # Hot path: memoize per [format, resolved class] — the hierarchy
  # walk below only runs on a miss. Format serializers register at
  # load time, so a hit is stable.
  cache = SERIALIZER_LOOKUP_CACHE
  key = [format, type_class]
  return cache[key] if cache.key?(key)

  klass = type_class
  found = nil
  while klass && klass <= Value
    s = @format_type_serializers[[format, klass]]
    if s
      found = s
      break
    end

    klass = klass.superclass
  end
  cache[key] = found
  found
end

.fraction_digits(count) ⇒ Object

Cap the number of significant fraction digits (xs:fractionDigits) an integer- or decimal-derived value may carry (a maximum).



197
198
199
# File 'lib/lutaml/model/type/value.rb', line 197

def fraction_digits(count)
  declare_digit_facet(:fraction_digits, count, minimum: 0)
end

.from_format(value, format) ⇒ Object

Class-level format conversion



332
333
334
# File 'lib/lutaml/model/type/value.rb', line 332

def self.from_format(value, format)
  new(send(:"from_#{format}", value))
end

.inclusive(min: nil, max: nil) ⇒ Object

Constrain ordered values via min/maxInclusive.



128
129
130
131
132
133
# File 'lib/lutaml/model/type/value.rb', line 128

def inclusive(min: nil, max: nil)
  min = cast_facet_value(min)
  max = cast_facet_value(max)
  raise_unordered!("inclusive", min, max)
  declare_facets(min_inclusive: min, max_inclusive: max)
end

.inherited(subclass) ⇒ Object

Freeze a type's facets to further declarations once it is subclassed, so a parent cannot widen facets after children exist.



94
95
96
97
# File 'lib/lutaml/model/type/value.rb', line 94

def inherited(subclass)
  super
  @facets_closed = true
end

.length(exact = nil, min: nil, max: nil) ⇒ Object

Constrain length: exact length N, or length min:, max:.



144
145
146
147
148
149
150
151
# File 'lib/lutaml/model/type/value.rb', line 144

def length(exact = nil, min: nil, max: nil)
  return declare_exact_length(exact, min, max) unless exact.nil?

  raise_unordered!("length", min, max)
  raise_negative_length!(min)
  raise_negative_length!(max)
  declare_facets(min_length: min, max_length: max)
end

.pattern(regex_or_string) ⇒ Object

Restrict a string-derived value to match a regular expression. A String argument is compiled to a Regexp. Patterns accumulate across the chain; a value must match all of them.



163
164
165
166
# File 'lib/lutaml/model/type/value.rb', line 163

def pattern(regex_or_string)
  regexp = regex_or_string.is_a?(Regexp) ? regex_or_string : Regexp.new(regex_or_string)
  declare_facets(pattern: [regexp])
end

.register_format_to_from_methods(format) ⇒ Object

Called from FormatRegistry when a new format is registered. Defines to_format and from_format methods that check the serializer registry first, falling back to default behavior.



339
340
341
342
343
344
345
346
347
348
349
# File 'lib/lutaml/model/type/value.rb', line 339

def self.register_format_to_from_methods(format)
  define_method(:"to_#{format}") do
    s = Value.format_type_serializer_for(format, self.class)
    s&.dig(:to) ? s[:to].call(self) : value
  end

  define_singleton_method(:"from_#{format}") do |v|
    s = Value.format_type_serializer_for(format, self)
    s&.dig(:from) ? s[:from].call(v) : cast(v)
  end
end

.register_format_type_serializer(format, type_class, to: nil, from: nil) ⇒ Object

Register a custom type serializer for a specific format and type class. Format plugins call this at load time to register their custom serialization logic.

Parameters:

  • format (Symbol)

    the format (e.g., :xml, :json)

  • type_class (Class)

    the type class (must be <= Value)

  • to (Proc, nil) (defaults to: nil)

    custom instance serialization proc (receives the type instance)

  • from (Proc, nil) (defaults to: nil)

    custom class deserialization proc (receives the raw value)



58
59
60
61
62
# File 'lib/lutaml/model/type/value.rb', line 58

def register_format_type_serializer(format, type_class, to: nil,
from: nil)
  @format_type_serializers[[format, type_class]] =
    { to: to, from: from }.compact
end

.serialize(value) ⇒ Object



319
320
321
322
323
324
# File 'lib/lutaml/model/type/value.rb', line 319

def self.serialize(value)
  return nil if value.nil?
  return value if Utils.uninitialized?(value)

  new(value).to_s
end

.tighter_facet(key, existing, incoming) ⇒ Object

Conjunctive-merge primitive for two facet values of the same key: the tighter one wins (greater for min-like, lesser for max-like), an exact :length must agree. The canonical home for facet combination, reused by the Layer-1/Layer-2 merge.

Raises:

  • (ArgumentError)


113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/lutaml/model/type/value.rb', line 113

def tighter_facet(key, existing, incoming)
  return incoming if existing.nil?
  return existing if incoming.nil?
  return incoming & existing if key == :enumeration
  return existing + incoming if LIST_FACETS.include?(key)
  return [existing, incoming].max if MIN_FACETS.include?(key)
  return [existing, incoming].min if MAX_FACETS.include?(key)
  return stricter_white_space(existing, incoming) if key == :white_space
  return existing if existing == incoming

  raise ArgumentError,
        "conflicting `#{key}` facets: #{existing} and #{incoming}"
end

.total_digits(count) ⇒ Object

Cap the total number of significant digits (xs:totalDigits) an integer- or decimal-derived value may carry. A maximum; the value must have at most count significant digits. Applicability to the resolved type is enforced lazily at validation time.



191
192
193
# File 'lib/lutaml/model/type/value.rb', line 191

def total_digits(count)
  declare_digit_facet(:total_digits, count, minimum: 1)
end

.white_space(mode) ⇒ Object

Normalize a string-derived value's whitespace at cast time (xs:whiteSpace). Unlike every other facet this transforms the stored value rather than validating it, so it is applied in String.cast, not the lazy validator. Only string-derived types carry lexical text, so declaring it elsewhere fails fast.



173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/lutaml/model/type/value.rb', line 173

def white_space(mode)
  unless self <= Type::String
    raise ArgumentError,
          "`white_space` is only allowed for string-derived types"
  end
  unless WHITE_SPACE_MODES.include?(mode)
    raise ArgumentError,
          "`white_space` must be one of " \
          "#{WHITE_SPACE_MODES.map(&:inspect).join(', ')}"
  end

  declare_facets(white_space: mode)
end

Instance Method Details

#initialized?Boolean

Returns:



308
309
310
# File 'lib/lutaml/model/type/value.rb', line 308

def initialized?
  true
end

#to_sObject

Instance methods for serialization



327
328
329
# File 'lib/lutaml/model/type/value.rb', line 327

def to_s
  value.to_s
end