typelizer 0.9.0 → 0.9.2

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: d8a6cb6187a0670a589346d969c61a17ae0b9fd60a4e6926c74c2e72c9594130
4
- data.tar.gz: 4ef1a1e6e728df5ab1d5198a0254cfa68e5e477da57fb9c3c6b7ae2f7efa945d
3
+ metadata.gz: 505210c62639d70e95336cfb02bb0ab952089ac8433d5816774977853af264a1
4
+ data.tar.gz: 2878868d86acf4da05caaf5e139e3ccacf58025b8bf5fc2bc56c04b0a91049b4
5
5
  SHA512:
6
- metadata.gz: 56296749f1d5478e98457afac0d2e8599e360d5b7864d596772c825e9970471de8b1d0103ada0120b9adeddab2805e65976c496ce0963785dfd39ddc3dffae91
7
- data.tar.gz: 25f8667ef0f54560e1c84e8d906b50abc55e01ea761dacff012657bc7d94f7127263166b81c4e317350ee89219f92a070aa042004f42cfad3a6482fe31a0c86f
6
+ metadata.gz: b1522f196e8372d77e253bba7f9515043f925aa0f4d8ac6c3b1a11b837424743bba3225774b9dbd65232e46befdabbcb440ef6cf3370edc61e9dece9bf204e2e
7
+ data.tar.gz: 6e8acdb494ec3f5167bbfe9f53422ebd26d4e9d8dab89ec832518b1acda47aae821a002dd954daa7605f273f323092e3622699dffad88805120dbf3d3ad2fa4e
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.2] - 2026-02-26
11
+
12
+ ### Fixed
13
+
14
+ - Fix string literal unions in OpenAPI and bracket-aware union type splitting. ([@skryukov])
15
+
16
+ ## [0.9.1] - 2026-02-26
17
+
18
+ ### Fixed
19
+
20
+ - Fix crash when using tuple types in serializers. ([@skryukov])
21
+ - Handle abstract ActiveRecord classes gracefully during type inference. ([@skryukov])
22
+
10
23
  ## [0.9.0] - 2026-02-26
11
24
 
12
25
  ### Added
@@ -413,7 +426,9 @@ and this project adheres to [Semantic Versioning].
413
426
  [@skryukov]: https://github.com/skryukov
414
427
  [@ventsislaf]: https://github.com/ventsislaf
415
428
 
416
- [Unreleased]: https://github.com/skryukov/typelizer/compare/v0.9.0...HEAD
429
+ [Unreleased]: https://github.com/skryukov/typelizer/compare/v0.9.2...HEAD
430
+ [0.9.2]: https://github.com/skryukov/typelizer/compare/v0.9.1...v0.9.2
431
+ [0.9.1]: https://github.com/skryukov/typelizer/compare/v0.9.0...v0.9.1
417
432
  [0.9.0]: https://github.com/skryukov/typelizer/compare/v0.8.0...v0.9.0
418
433
  [0.8.0]: https://github.com/skryukov/typelizer/compare/v0.7.0...v0.8.0
419
434
  [0.7.0]: https://github.com/skryukov/typelizer/compare/v0.6.0...v0.7.0
@@ -177,7 +177,7 @@ module Typelizer
177
177
  end
178
178
 
179
179
  def extract_typescript_types(type)
180
- type.split(/[<>\[\],\s|]+/)
180
+ type.split(/[<>\[\],\s|]+/).reject(&:empty?)
181
181
  end
182
182
 
183
183
  def global_type?(type)
@@ -19,7 +19,7 @@ module Typelizer
19
19
  end
20
20
 
21
21
  def comment_for(prop)
22
- column = model_class&.columns_hash&.dig(prop.column_name.to_s)
22
+ column = columns_hash&.dig(prop.column_name.to_s)
23
23
  return nil unless column
24
24
 
25
25
  prop.comment = column.comment
@@ -35,6 +35,20 @@ module Typelizer
35
35
 
36
36
  private
37
37
 
38
+ def columns_hash
39
+ return nil unless model_class
40
+ return nil if model_class.abstract_class?
41
+
42
+ model_class.columns_hash
43
+ end
44
+
45
+ def attribute_types
46
+ return nil unless model_class&.respond_to?(:attribute_types)
47
+ return nil if model_class.abstract_class?
48
+
49
+ model_class.attribute_types
50
+ end
51
+
38
52
  def infer_types_for_association(prop)
39
53
  association = model_class&.reflect_on_association(prop.column_name.to_sym)
40
54
  return nil unless association
@@ -42,7 +56,7 @@ module Typelizer
42
56
  case association.macro
43
57
  when :belongs_to
44
58
  foreign_key = association.foreign_key
45
- column = model_class&.columns_hash&.dig(foreign_key.to_s)
59
+ column = columns_hash&.dig(foreign_key.to_s)
46
60
  if config.associations_strategy == :database
47
61
  prop.nullable = column.null if column
48
62
  elsif config.associations_strategy == :active_record
@@ -64,7 +78,7 @@ module Typelizer
64
78
  end
65
79
 
66
80
  def infer_types_for_column(prop)
67
- column = model_class&.columns_hash&.dig(prop.column_name.to_s)
81
+ column = columns_hash&.dig(prop.column_name.to_s)
68
82
  return nil unless column
69
83
 
70
84
  prop.column_type = column.type
@@ -122,9 +136,7 @@ module Typelizer
122
136
  end
123
137
 
124
138
  def infer_types_for_attribute(prop)
125
- return nil unless model_class.respond_to?(:attribute_types)
126
-
127
- attribute_type_obj = model_class.attribute_types.fetch(prop.column_name.to_s, nil)
139
+ attribute_type_obj = attribute_types&.fetch(prop.column_name.to_s, nil)
128
140
  return nil unless attribute_type_obj
129
141
 
130
142
  if attribute_type_obj.instance_of?(::ActiveRecord::Type::Serialized)
@@ -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
@@ -203,7 +209,17 @@ module Typelizer
203
209
  end
204
210
 
205
211
  def ts_only_type?(type_str)
206
- type_str.start_with?("{") || type_str.include?("<") || TS_OBJECT_TYPES.include?(type_str)
212
+ type_str.start_with?("{", "[") || type_str.include?("<") || TS_OBJECT_TYPES.include?(type_str)
213
+ end
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]
207
223
  end
208
224
 
209
225
  def validate_version!(openapi_version)
@@ -40,7 +40,11 @@ module Typelizer
40
40
  private
41
41
 
42
42
  def parse_union(type_str, **options)
43
- parts = type_str.split(/\s*\|\s*/)
43
+ parts = UnionTypeSorter.split_union_members(type_str)
44
+
45
+ # No top-level | found — the | is nested inside brackets
46
+ return {type: type_str.to_sym}.merge(options) if parts.size <= 1
47
+
44
48
  options[:nullable] = true if parts.delete("null")
45
49
  if parts.size == 1
46
50
  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.0"
4
+ VERSION = "0.9.2"
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.0
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svyatoslav Kryukov