sashite-pcn 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,194 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Sashite
4
- module Pcn
5
- # Immutable representation of player information for both sides.
6
- #
7
- # Contains player information for first and second player.
8
- # At least one player must be defined when sides are present.
9
- #
10
- # @see https://sashite.dev/specs/pcn/1.0.0/
11
- class Sides
12
- # @return [Player, nil] First player information
13
- attr_reader :first
14
-
15
- # @return [Player, nil] Second player information
16
- attr_reader :second
17
-
18
- # Parse a sides hash into a Sides object.
19
- #
20
- # @param hash [Hash] Sides hash
21
- # @return [Sides] Immutable sides object
22
- # @raise [Error::Validation] If validation fails
23
- #
24
- # @example
25
- # sides = Sides.parse({
26
- # "first" => { "name" => "Alice", "elo" => 2800 },
27
- # "second" => { "name" => "Bob", "elo" => 2750 }
28
- # })
29
- def self.parse(hash)
30
- raise Error::Validation, "Sides must be a Hash, got #{hash.class}" unless hash.is_a?(::Hash)
31
-
32
- first = parse_player(hash["first"], "first")
33
- second = parse_player(hash["second"], "second")
34
-
35
- new(first:, second:)
36
- end
37
-
38
- # Validate a sides hash without raising exceptions.
39
- #
40
- # @param hash [Hash] Sides hash
41
- # @return [Boolean] true if valid, false otherwise
42
- #
43
- # @example
44
- # Sides.valid?({ "first" => { "name" => "Alice" } }) # => true
45
- def self.valid?(hash)
46
- parse(hash)
47
- true
48
- rescue Error
49
- false
50
- end
51
-
52
- # Create a new Sides.
53
- #
54
- # @param first [Player, Hash, nil] First player information
55
- # @param second [Player, Hash, nil] Second player information
56
- # @raise [Error::Validation] If validation fails
57
- #
58
- # @example
59
- # sides = Sides.new(
60
- # first: Player.new(name: "Alice", elo: 2800),
61
- # second: Player.new(name: "Bob", elo: 2750)
62
- # )
63
- def initialize(first: nil, second: nil)
64
- @first = normalize_player(first, "first")
65
- @second = normalize_player(second, "second")
66
-
67
- validate!
68
-
69
- freeze
70
- end
71
-
72
- # Check if the sides are valid.
73
- #
74
- # @return [Boolean] true if valid
75
- def valid?
76
- validate!
77
- true
78
- rescue Error
79
- false
80
- end
81
-
82
- # Check if both sides are empty.
83
- #
84
- # @return [Boolean] true if both players are nil
85
- def empty?
86
- first.nil? && second.nil?
87
- end
88
-
89
- # Convert to hash representation.
90
- #
91
- # @return [Hash] Sides hash (excludes nil values)
92
- #
93
- # @example
94
- # sides.to_h # => { "first" => {...}, "second" => {...} }
95
- def to_h
96
- hash = {}
97
-
98
- hash["first"] = first.to_h unless first.nil?
99
- hash["second"] = second.to_h unless second.nil?
100
-
101
- hash
102
- end
103
-
104
- # String representation.
105
- #
106
- # @return [String] Inspectable representation
107
- def to_s
108
- fields = []
109
- fields << "first=#{first.name.inspect}" if first && first.name
110
- fields << "second=#{second.name.inspect}" if second && second.name
111
-
112
- "#<#{self.class} #{fields.join(' ')}>"
113
- end
114
- alias inspect to_s
115
-
116
- # Equality comparison.
117
- #
118
- # @param other [Sides] Other sides
119
- # @return [Boolean] true if equal
120
- def ==(other)
121
- other.is_a?(self.class) &&
122
- other.first == first &&
123
- other.second == second
124
- end
125
- alias eql? ==
126
-
127
- # Hash code for equality.
128
- #
129
- # @return [Integer] Hash code
130
- def hash
131
- [self.class, first, second].hash
132
- end
133
-
134
- private
135
-
136
- # Parse player field.
137
- def self.parse_player(value, field_name)
138
- return nil if value.nil?
139
-
140
- Player.parse(value)
141
- rescue Error => e
142
- raise Error::Validation, "Invalid '#{field_name}' player: #{e.message}"
143
- end
144
-
145
- # Normalize player to Player object.
146
- def normalize_player(value, field_name)
147
- return nil if value.nil?
148
- return value if value.is_a?(Player)
149
-
150
- Player.parse(value)
151
- rescue Error => e
152
- raise Error::Validation, "Invalid '#{field_name}' player: #{e.message}"
153
- end
154
-
155
- # Validate all fields.
156
- def validate!
157
- validate_structure!
158
- validate_first!
159
- validate_second!
160
- end
161
-
162
- # Validate that at least one player is defined.
163
- def validate_structure!
164
- return unless first.nil? && second.nil?
165
-
166
- raise Error::Validation, "Sides must have at least one player defined"
167
- end
168
-
169
- # Validate first player.
170
- def validate_first!
171
- return if first.nil?
172
-
173
- raise Error::Validation, "Sides 'first' must be a Player object, got #{first.class}" unless first.is_a?(Player)
174
-
175
- return if first.valid?
176
-
177
- raise Error::Validation, "Sides 'first' player validation failed"
178
- end
179
-
180
- # Validate second player.
181
- def validate_second!
182
- return if second.nil?
183
-
184
- unless second.is_a?(Player)
185
- raise Error::Validation, "Sides 'second' must be a Player object, got #{second.class}"
186
- end
187
-
188
- return if second.valid?
189
-
190
- raise Error::Validation, "Sides 'second' player validation failed"
191
- end
192
- end
193
- end
194
- end