typelizer 0.9.1 → 0.9.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82ca16632db02541212afb1fa0cd9b47bbaa10a375b68c4d830e4264eeec5fd8
4
- data.tar.gz: c12d5e0a2aea772483d92b8290dcfa9c6a5e5e38febebf3d986db126f00e4d87
3
+ metadata.gz: 9e0f1018fd5d37ab4171501db8961c7895e796f1f41bf292c773f6d39fa08832
4
+ data.tar.gz: 3ab825f73e774cb8d13bb61772c878501e25b42f7e1b3ad35ae2a7a6a31e2d32
5
5
  SHA512:
6
- metadata.gz: 5e04629e8669b5ed6c610c85246ea06d3bda46100d2d9d876ad2d0ae6904669aeb569b689b4634df39217a392028bb968060f30a7b4f1f6a47c6374c0604d84d
7
- data.tar.gz: ba808f9f614f856fcd6f3c496b990bbb1ead4530e0214d3d55cbadd62a63e5a9846623ff446d006570d5b4c2b499bfe7bb46c9049c95dc74caf9df81e7a80187
6
+ metadata.gz: 31b6a138ee7ae3c95f173f2df1f2168469b285aeff266c350c27a95b51021fa2deeb43bea1f9b28a0f9a4114a6ff4af9f82e47e64f04dcf314c88dfa9d042936
7
+ data.tar.gz: ccd0cb33a718fedae63e46e83bc7495d6b3520d7bfe6b190e2021e98f0f986ed36ca970dd25a71acbcc9017fd1b4b7579fb2399cf6727c92c2a13a1f38d84d80
data/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning].
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.9.3] - 2026-02-27
11
+
12
+ ### Fixed
13
+
14
+ - Fix keyless `typelize` DSL without name. ([@skryukov])
15
+ - Support arrays for keyless `typelize` calls. ([@skryukov])
16
+
17
+ ## [0.9.2] - 2026-02-26
18
+
19
+ ### Fixed
20
+
21
+ - Fix string literal unions in OpenAPI and bracket-aware union type splitting. ([@skryukov])
22
+
10
23
  ## [0.9.1] - 2026-02-26
11
24
 
12
25
  ### Fixed
@@ -420,7 +433,9 @@ and this project adheres to [Semantic Versioning].
420
433
  [@skryukov]: https://github.com/skryukov
421
434
  [@ventsislaf]: https://github.com/ventsislaf
422
435
 
423
- [Unreleased]: https://github.com/skryukov/typelizer/compare/v0.9.1...HEAD
436
+ [Unreleased]: https://github.com/skryukov/typelizer/compare/v0.9.3...HEAD
437
+ [0.9.3]: https://github.com/skryukov/typelizer/compare/v0.9.2...v0.9.3
438
+ [0.9.2]: https://github.com/skryukov/typelizer/compare/v0.9.1...v0.9.2
424
439
  [0.9.1]: https://github.com/skryukov/typelizer/compare/v0.9.0...v0.9.1
425
440
  [0.9.0]: https://github.com/skryukov/typelizer/compare/v0.8.0...v0.9.0
426
441
  [0.8.0]: https://github.com/skryukov/typelizer/compare/v0.7.0...v0.8.0
@@ -29,7 +29,7 @@ module Typelizer
29
29
  return unless keyless_type
30
30
 
31
31
  type, attrs = keyless_type
32
- typelize(name => [type, attrs])
32
+ store_type(:_typelizer_attributes, name, attrs.merge(type: type))
33
33
  self.keyless_type = nil
34
34
  end
35
35
 
data/lib/typelizer/dsl.rb CHANGED
@@ -73,11 +73,26 @@ module Typelizer
73
73
  assign_type_information(:_typelizer_meta_attributes, attributes)
74
74
  end
75
75
 
76
+ def store_type(attribute_name, name, options)
77
+ ensure_type_store(attribute_name)
78
+ instance_variable_get("@#{attribute_name}")[name.to_sym] ||= {}
79
+ instance_variable_get("@#{attribute_name}")[name.to_sym].merge!(options)
80
+ end
81
+
76
82
  private
77
83
 
78
84
  def assign_type_information(attribute_name, attributes)
79
85
  return unless Typelizer.enabled?
80
86
 
87
+ attributes.each do |name, attrs|
88
+ next unless name
89
+
90
+ options = parse_type_declaration(attrs)
91
+ store_type(attribute_name, name, options)
92
+ end
93
+ end
94
+
95
+ def ensure_type_store(attribute_name)
81
96
  instance_variable = "@#{attribute_name}"
82
97
 
83
98
  unless instance_variable_get(instance_variable)
@@ -96,29 +111,26 @@ module Typelizer
96
111
  end
97
112
  end
98
113
  end
114
+ end
99
115
 
100
- attributes.each do |name, attrs|
101
- next unless name
102
-
103
- attrs = [attrs] if attrs && !attrs.is_a?(Array)
104
- options = attrs.last.is_a?(Hash) ? attrs.pop : {}
105
-
106
- if attrs.any?
107
- parsed_types = attrs.map { |t| TypeParser.parse(t) }
108
- all_types = parsed_types.flat_map { |p| Array(p[:type]) }
109
- parsed_types.each do |parsed|
110
- options[:optional] = true if parsed[:optional]
111
- options[:multi] = true if parsed[:multi]
112
- options[:nullable] = true if parsed[:nullable]
113
- end
114
- options[:nullable] = true if all_types.delete(:null)
115
- # Unwrap single-element arrays: typelize field: ["string"] behaves like typelize field: "string"
116
- options[:type] = (all_types.size == 1) ? all_types.first : all_types
116
+ def parse_type_declaration(attrs)
117
+ attrs = [attrs] if attrs && !attrs.is_a?(Array)
118
+ options = attrs.last.is_a?(Hash) ? attrs.pop : {}
119
+
120
+ if attrs.any?
121
+ parsed_types = attrs.map { |t| TypeParser.parse(t) }
122
+ all_types = parsed_types.flat_map { |p| Array(p[:type]) }
123
+ parsed_types.each do |parsed|
124
+ options[:optional] = true if parsed[:optional]
125
+ options[:multi] = true if parsed[:multi]
126
+ options[:nullable] = true if parsed[:nullable]
117
127
  end
118
-
119
- instance_variable_get(instance_variable)[name.to_sym] ||= {}
120
- instance_variable_get(instance_variable)[name.to_sym].merge!(options)
128
+ options[:nullable] = true if all_types.delete(:null)
129
+ # Unwrap single-element arrays: typelize field: ["string"] behaves like typelize field: "string"
130
+ options[:type] = (all_types.size == 1) ? all_types.first : all_types
121
131
  end
132
+
133
+ options
122
134
  end
123
135
  end
124
136
  end
@@ -89,9 +89,11 @@ module Typelizer
89
89
  end
90
90
 
91
91
  def union_schema(property, openapi_version:)
92
- schemas = property.type.map { |part| union_member_schema(part) }
93
-
94
- definition = {anyOf: schemas}
92
+ definition = if property.type.all? { |part| string_literal?(part) }
93
+ {type: :string, enum: property.type.map { |part| unquote_string_literal(part) }}
94
+ else
95
+ {anyOf: property.type.map { |part| union_member_schema(part) }}
96
+ end
95
97
 
96
98
  unless property.multi
97
99
  apply_nullable(definition, property, openapi_version: openapi_version)
@@ -154,6 +156,8 @@ module Typelizer
154
156
  elsif definition[:type]
155
157
  v31?(openapi_version) ? definition[:type] = [definition[:type], :null] : definition[:nullable] = true
156
158
  end
159
+
160
+ definition[:enum] << nil if definition[:enum].is_a?(Array) && !definition[:enum].include?(nil)
157
161
  end
158
162
 
159
163
  def wrap_multi(definition, property, openapi_version:)
@@ -180,6 +184,8 @@ module Typelizer
180
184
  result = COLUMN_TYPE_MAP[property.column_type].dup
181
185
  result[:type] = :string if property.enum
182
186
  result
187
+ elsif string_literal?(property.type)
188
+ {type: :string, enum: [unquote_string_literal(property.type)]}
183
189
  elsif (property.type.is_a?(String) || property.type.is_a?(Symbol)) && !OPENAPI_TYPES.include?(property.type.to_sym) && !ts_only_type?(property.type.to_s)
184
190
  {"$ref" => "#/components/schemas/#{property.type}"}
185
191
  else
@@ -206,6 +212,16 @@ module Typelizer
206
212
  type_str.start_with?("{", "[") || type_str.include?("<") || TS_OBJECT_TYPES.include?(type_str)
207
213
  end
208
214
 
215
+ def string_literal?(type)
216
+ str = type.to_s
217
+ str.length > 2 &&
218
+ ((str.start_with?("'") && str.end_with?("'")) || (str.start_with?('"') && str.end_with?('"')))
219
+ end
220
+
221
+ def unquote_string_literal(type)
222
+ type.to_s[1..-2]
223
+ end
224
+
209
225
  def validate_version!(openapi_version)
210
226
  raise ArgumentError, "Unsupported openapi_version: #{openapi_version}. Must be one of: #{SUPPORTED_VERSIONS.join(", ")}" unless SUPPORTED_VERSIONS.include?(openapi_version.to_s)
211
227
  end
@@ -12,6 +12,7 @@ module Typelizer
12
12
  class << self
13
13
  def parse(type_def, **options)
14
14
  return options if type_def.nil?
15
+ return parse_array(type_def, **options) if type_def.is_a?(Array)
15
16
 
16
17
  type_str = type_def.to_s
17
18
  return parse_union(type_str, **options) if type_str.include?("|")
@@ -39,8 +40,31 @@ module Typelizer
39
40
 
40
41
  private
41
42
 
43
+ def parse_array(type_defs, **options)
44
+ parsed = type_defs.map { |t| parse(t) }
45
+ types = parsed.flat_map { |p| Array(p[:type]) }
46
+
47
+ parsed.each do |p|
48
+ options[:optional] = true if p[:optional]
49
+ options[:multi] = true if p[:multi]
50
+ options[:nullable] = true if p[:nullable]
51
+ end
52
+
53
+ options[:nullable] = true if types.delete(:null)
54
+
55
+ if types.size == 1
56
+ {type: types.first}.merge(options)
57
+ else
58
+ {type: types}.merge(options)
59
+ end
60
+ end
61
+
42
62
  def parse_union(type_str, **options)
43
- parts = type_str.split(/\s*\|\s*/)
63
+ parts = UnionTypeSorter.split_union_members(type_str)
64
+
65
+ # No top-level | found — the | is nested inside brackets
66
+ return {type: type_str.to_sym}.merge(options) if parts.size <= 1
67
+
44
68
  options[:nullable] = true if parts.delete("null")
45
69
  if parts.size == 1
46
70
  parse(parts.first, **options)
@@ -98,10 +98,10 @@ module Typelizer
98
98
 
99
99
  str.each_char do |char|
100
100
  case char
101
- when "<", "("
101
+ when "<", "(", "{", "["
102
102
  depth += 1
103
103
  current << char
104
- when ">", ")"
104
+ when ">", ")", "}", "]"
105
105
  depth -= 1
106
106
  current << char
107
107
  when "|"
@@ -126,6 +126,8 @@ module Typelizer
126
126
  def self.balanced_brackets?(str)
127
127
  angle_depth = 0
128
128
  paren_depth = 0
129
+ brace_depth = 0
130
+ bracket_depth = 0
129
131
 
130
132
  str.each_char do |char|
131
133
  case char
@@ -139,10 +141,20 @@ module Typelizer
139
141
  when ")"
140
142
  paren_depth -= 1
141
143
  return false if paren_depth < 0
144
+ when "{"
145
+ brace_depth += 1
146
+ when "}"
147
+ brace_depth -= 1
148
+ return false if brace_depth < 0
149
+ when "["
150
+ bracket_depth += 1
151
+ when "]"
152
+ bracket_depth -= 1
153
+ return false if bracket_depth < 0
142
154
  end
143
155
  end
144
156
 
145
- angle_depth == 0 && paren_depth == 0
157
+ angle_depth == 0 && paren_depth == 0 && brace_depth == 0 && bracket_depth == 0
146
158
  end
147
159
  end
148
160
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Typelizer
4
- VERSION = "0.9.1"
4
+ VERSION = "0.9.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typelizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svyatoslav Kryukov