typelizer 0.7.0 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 819c25a9bfc5dd74c3b7c77045bb5f5ef968f518f8a8d228502badb6936ea50d
4
- data.tar.gz: c8d8d9affec2d6eb07cf1e58942fe46009fad2782c57689218b8357a85c227f0
3
+ metadata.gz: a0b21f5f78121c64717aa08d6556efbddc0b0b80a3ec6f272d63f699133fff12
4
+ data.tar.gz: d8df7c3542950c82e6b25d3f9c39ea808aded01fab0e5b5eaada3801a99031a3
5
5
  SHA512:
6
- metadata.gz: b4b39192ff398bb25c6b537fcb5c73893de40d82f14a09a254e4ef8317a08232819c3285b7e6806938f1c7058f895d0100b03884574158bbaa2e329982518683
7
- data.tar.gz: 8971c5a2cc78992fd38dc101e7b2fc1eaefe82ad756f828bca4416a4a6ed54d1ff25eace61fd80cd293cca3dc66b6b6c24706a0fa45156fb275eb9bfe4976c46
6
+ metadata.gz: cc42979e0f00e3aec338fdd4cf3848639ba12a2019293a46e864e908a1dfd8f2f2258ef62d07ed15ec3da66ca7ad740907ac63c6e04f9aae29f18db0eae46584
7
+ data.tar.gz: c9504167e013d6aeff1208207b4531f012fe942018e72368296d2a676fb04460ece7b0582a5ef297cf6c037e071ea0868e39c4632765030bdb014fe9a49884ea
data/CHANGELOG.md CHANGED
@@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning].
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-02-19
11
+
12
+ ### Added
13
+
14
+ - OpenAPI schema generation from serializers, supporting both OpenAPI 3.0 and 3.1. ([@skryukov])
15
+
16
+ ```ruby
17
+ # Get all schemas as a hash
18
+ Typelizer.openapi_schemas
19
+ # => { "Post" => { type: :object, properties: { ... }, required: [...] }, ... }
20
+
21
+ # OpenAPI 3.1 output
22
+ Typelizer.openapi_schemas(openapi_version: "3.1")
23
+ ```
24
+
25
+ Column types are automatically mapped to OpenAPI types with proper formats (`integer`, `int64`, `uuid`, `date-time`, etc.).
26
+ Enums, nullable fields, arrays, deprecated flags, and `$ref` associations are all handled automatically.
27
+
28
+ - Type inference for delegated attributes (`delegate :name, to: :user`). Typelizer now tracks `delegate` calls on ActiveRecord models and resolves types from the target association's model, including support for `prefix` and `allow_nil` options. ([@skryukov])
29
+
30
+ - Reference other serializers in `typelize` method by passing the class directly. ([@skryukov])
31
+
32
+ - Per-writer `reject_class` configuration. Each writer can now define its own `reject_class` filter, enabling scoped output (e.g., only V1 serializers for a V1 writer). ([@skryukov])
33
+
34
+ ### Fixed
35
+
36
+ - `typelize` DSL metadata (optional, comment, type overrides) now correctly applies to renamed attributes (e.g., via `key:`, `alias_name`, `value_from`). Previously, metadata was looked up only by `column_name`, missing attributes where the output name differs. ([@skryukov])
37
+
10
38
  ## [0.7.0] - 2026-01-15
11
39
 
12
40
  ### Changed
@@ -358,7 +386,8 @@ and this project adheres to [Semantic Versioning].
358
386
  [@prog-supdex]: https://github.com/prog-supdex
359
387
  [@ventsislaf]: https://github.com/ventsislaf
360
388
 
361
- [Unreleased]: https://github.com/skryukov/typelizer/compare/v0.7.0...HEAD
389
+ [Unreleased]: https://github.com/skryukov/typelizer/compare/v0.8.0...HEAD
390
+ [0.8.0]: https://github.com/skryukov/typelizer/compare/v0.7.0...v0.8.0
362
391
  [0.7.0]: https://github.com/skryukov/typelizer/compare/v0.6.0...v0.7.0
363
392
  [0.6.0]: https://github.com/skryukov/typelizer/compare/v0.5.6...v0.6.0
364
393
  [0.5.6]: https://github.com/skryukov/typelizer/compare/v0.5.5...v0.5.6
data/README.md CHANGED
@@ -16,6 +16,7 @@ Typelizer generates TypeScript types from your Ruby serializers. It supports mul
16
16
  - [Manual Generation](#manual-generation)
17
17
  - [Automatic Generation in Development](#automatic-generation-in-development)
18
18
  - [Disabling Typelizer](#disabling-typelizer)
19
+ - [OpenAPI Schema Generation](#openapi-schema-generation)
19
20
  - [Configuration](#configuration)
20
21
  - [Global Configuration](#simple-configuration)
21
22
  - [Writers (multiple outputs)](#defining-multiple-writers)
@@ -128,6 +129,26 @@ class PostResource < ApplicationResource
128
129
  end
129
130
  ```
130
131
 
132
+ You can reference other serializers directly by passing the class. Typelizer resolves the class to its generated type name automatically:
133
+
134
+ ```ruby
135
+ class PostResource < ApplicationResource
136
+ attributes :id, :title
137
+
138
+ # Reference another serializer — resolves to its generated TypeScript type
139
+ typelize reviewer: [AuthorResource, {optional: true, nullable: true}]
140
+ attribute :reviewer do |post|
141
+ post.reviewer
142
+ end
143
+
144
+ # Self-reference works too
145
+ typelize previous_post: PostResource
146
+ attribute :previous_post do |post|
147
+ post.previous_post
148
+ end
149
+ end
150
+ ```
151
+
131
152
  For more complex type definitions, use the full API:
132
153
 
133
154
  ```ruby
@@ -310,6 +331,60 @@ Typelizer.listen = false
310
331
 
311
332
  Sometimes we want to use Typelizer only with manual generation. To disable Typelizer during development, we can set `DISABLE_TYPELIZER` environment variable to `true`. This doesn't affect manual generation.
312
333
 
334
+ ## OpenAPI Schema Generation
335
+
336
+ Typelizer can generate [OpenAPI](https://swagger.io/specification/) component schemas from your serializers. This is useful for documenting your API or integrating with tools like [rswag](https://github.com/rswag/rswag).
337
+
338
+ Get all schemas as a hash:
339
+
340
+ ```ruby
341
+ Typelizer.openapi_schemas
342
+ # => {
343
+ # "Post" => {
344
+ # type: :object,
345
+ # properties: {
346
+ # id: { type: :integer },
347
+ # title: { type: :string },
348
+ # published_at: { type: :string, format: :"date-time", nullable: true }
349
+ # },
350
+ # required: [:id, :title]
351
+ # },
352
+ # "Author" => { ... }
353
+ # }
354
+ ```
355
+
356
+ By default, schemas are generated for OpenAPI 3.0. Pass `openapi_version: "3.1"` for OpenAPI 3.1 output (e.g., `type: [:string, :null]` instead of `nullable: true`):
357
+
358
+ ```ruby
359
+ Typelizer.openapi_schemas(openapi_version: "3.1")
360
+ ```
361
+
362
+ Generate a schema for a single interface:
363
+
364
+ ```ruby
365
+ interfaces = Typelizer.interfaces
366
+ post_interface = interfaces.find { |i| i.name == "Post" }
367
+ Typelizer::OpenAPI.schema_for(post_interface)
368
+ Typelizer::OpenAPI.schema_for(post_interface, openapi_version: "3.1")
369
+ ```
370
+
371
+ Column types are mapped to OpenAPI types automatically:
372
+
373
+ | Column type | OpenAPI type | Format |
374
+ |---|---|---|
375
+ | `integer` | `integer` | |
376
+ | `bigint` | `integer` | `int64` |
377
+ | `float` | `number` | `float` |
378
+ | `decimal` | `number` | `double` |
379
+ | `boolean` | `boolean` | |
380
+ | `string`, `text`, `citext` | `string` | |
381
+ | `uuid` | `string` | `uuid` |
382
+ | `date` | `string` | `date` |
383
+ | `datetime` | `string` | `date-time` |
384
+ | `time` | `string` | `time` |
385
+
386
+ Enums, nullable fields, arrays, deprecated flags, and `$ref` associations are all handled automatically.
387
+
313
388
  ## Configuration
314
389
 
315
390
  Typelizer provides several global configuration options:
@@ -19,13 +19,13 @@ namespace :typelizer do
19
19
  ENV["DISABLE_TYPELIZER"] = "false"
20
20
 
21
21
  puts "Generating TypeScript interfaces..."
22
- serializers = []
23
22
  time = Benchmark.realtime do
24
- serializers = block.call
23
+ block.call
25
24
  end
26
25
 
26
+ interfaces = Typelizer.interfaces
27
27
  puts "Finished in #{time} seconds"
28
- puts "Found #{serializers.size} serializers:"
29
- puts serializers.map { |s| "\t#{s.name}" }.join("\n")
28
+ puts "Found #{interfaces.size} serializers:"
29
+ puts interfaces.map { |i| "\t#{i.name}" }.join("\n")
30
30
  end
31
31
  end
@@ -51,6 +51,7 @@ module Typelizer
51
51
  output_dir
52
52
  inheritance_strategy
53
53
  associations_strategy
54
+ reject_class
54
55
  ].freeze
55
56
 
56
57
  Config = Struct.new(
@@ -70,6 +71,7 @@ module Typelizer
70
71
  :verbatim_module_syntax,
71
72
  :inheritance_strategy,
72
73
  :associations_strategy,
74
+ :reject_class,
73
75
  :comments,
74
76
  :prefer_double_quotes,
75
77
  keyword_init: true
@@ -105,6 +107,7 @@ module Typelizer
105
107
  null_strategy: :nullable,
106
108
  inheritance_strategy: :none,
107
109
  associations_strategy: :database,
110
+ reject_class: ->(serializer:) { false },
108
111
  comments: false,
109
112
  prefer_double_quotes: false,
110
113
 
@@ -18,12 +18,11 @@ module Typelizer
18
18
  class Configuration
19
19
  DEFAULT_WRITER_NAME = :default
20
20
 
21
- attr_accessor :dirs, :reject_class, :listen
21
+ attr_accessor :dirs, :listen
22
22
  attr_reader :writers, :global_settings
23
23
 
24
24
  def initialize
25
25
  @dirs = []
26
- @reject_class = ->(serializer:) { false }
27
26
  @listen = nil
28
27
 
29
28
  default = Config.build
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typelizer
4
+ module DelegateTracker
5
+ @registry = {} # { Class => { method_name => { to:, allow_nil:, original: } } }
6
+
7
+ class << self
8
+ attr_reader :registry
9
+
10
+ def [](klass, method)
11
+ registry.dig(klass, method)
12
+ end
13
+ end
14
+
15
+ module Hook
16
+ def delegate(*methods, to:, allow_nil: nil, prefix: nil, **)
17
+ super.tap do
18
+ next unless is_a?(Class) && defined?(ActiveRecord::Base) && self < ActiveRecord::Base
19
+
20
+ method_prefix = if prefix == true
21
+ "#{to}_"
22
+ else
23
+ prefix ? "#{prefix}_" : ""
24
+ end
25
+ methods.each do |m|
26
+ (DelegateTracker.registry[self] ||= {})[:"#{method_prefix}#{m}"] = {to: to, allow_nil: !!allow_nil, original: m.to_sym}
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ Module.prepend(Typelizer::DelegateTracker::Hook) if Typelizer.enabled?
@@ -9,37 +9,12 @@ module Typelizer
9
9
  def call(force: false)
10
10
  return [] unless Typelizer.enabled?
11
11
 
12
- load_serializers
13
- serializers = target_serializers
14
-
15
12
  Typelizer.configuration.writers.each do |writer_name, writer_config|
16
- context = WriterContext.new(writer_name: writer_name)
17
- interfaces = serializers.map { |klass| context.interface_for(klass) }
13
+ interfaces = Typelizer.interfaces(writer_name: writer_name)
14
+ raise ArgumentError, "No serializers found. Please ensure all your serializers include Typelizer::DSL." if interfaces.empty?
18
15
 
19
16
  Writer.new(writer_config).call(interfaces, force: force)
20
17
  end
21
-
22
- serializers
23
- end
24
-
25
- private
26
-
27
- def load_serializers
28
- Typelizer.dirs.flat_map { |dir| Dir["#{dir}/**/*.rb"] }.each do |file|
29
- require file
30
- end
31
- end
32
-
33
- def target_serializers
34
- base_classes = Typelizer.base_classes.filter_map do |base_class|
35
- Object.const_get(base_class) if Object.const_defined?(base_class)
36
- end.tap do |base_classes|
37
- raise ArgumentError, "Please ensure all your serializers include Typelizer::DSL." if base_classes.none?
38
- end
39
-
40
- (base_classes + base_classes.flat_map(&:descendants)).uniq
41
- .reject { |serializer| Typelizer.reject_class.call(serializer: serializer) }
42
- .sort_by(&:name)
43
18
  end
44
19
  end
45
20
  end
@@ -175,7 +175,7 @@ module Typelizer
175
175
  multi_attrs = serializer.respond_to?(:_typelizer_multi_attributes) ? serializer._typelizer_multi_attributes : Set.new
176
176
 
177
177
  props.map do |prop|
178
- has_dsl = dsl_attrs[prop.column_name.to_sym]&.any?
178
+ has_dsl = dsl_attrs_for(prop, dsl_attrs)&.any?
179
179
 
180
180
  prop
181
181
  .then { |p| apply_dsl_type(p, dsl_attrs) }
@@ -185,13 +185,30 @@ module Typelizer
185
185
  end
186
186
  end
187
187
 
188
+ def dsl_attrs_for(prop, dsl_attrs)
189
+ dsl_attrs[prop.column_name.to_sym] || dsl_attrs[prop.name.to_sym]
190
+ end
191
+
188
192
  def apply_dsl_type(prop, dsl_attrs)
189
- dsl_type = dsl_attrs[prop.column_name.to_sym]
193
+ dsl_type = dsl_attrs_for(prop, dsl_attrs)
190
194
  return prop unless dsl_type&.any?
191
195
 
196
+ dsl_type = resolve_class_type(dsl_type)
192
197
  prop.with(**dsl_type)
193
198
  end
194
199
 
200
+ def resolve_class_type(attrs)
201
+ type = attrs[:type]
202
+ return attrs unless type.is_a?(String) || type.is_a?(Symbol)
203
+
204
+ klass = Object.const_get(type.to_s)
205
+ return attrs unless klass.respond_to?(:typelizer_config)
206
+
207
+ attrs.merge(type: context.interface_for(klass))
208
+ rescue NameError
209
+ attrs
210
+ end
211
+
195
212
  def apply_model_inference(prop)
196
213
  model_plugin.infer_types(prop)
197
214
  end
@@ -12,6 +12,7 @@ module Typelizer
12
12
  infer_types_for_association(prop) ||
13
13
  infer_types_for_column(prop) ||
14
14
  infer_types_for_association_ids(prop) ||
15
+ infer_types_for_delegate(prop) ||
15
16
  infer_types_for_attribute(prop)
16
17
 
17
18
  prop
@@ -66,6 +67,7 @@ module Typelizer
66
67
  column = model_class&.columns_hash&.dig(prop.column_name.to_s)
67
68
  return nil unless column
68
69
 
70
+ prop.column_type = column.type
69
71
  prop.multi = !!column.try(:array)
70
72
  case config.null_strategy
71
73
  when :nullable
@@ -100,6 +102,25 @@ module Typelizer
100
102
  prop
101
103
  end
102
104
 
105
+ def infer_types_for_delegate(prop)
106
+ return nil unless model_class
107
+
108
+ info = DelegateTracker[model_class, prop.column_name.to_sym]
109
+ return nil unless info
110
+
111
+ assoc = model_class.reflect_on_association(info[:to])
112
+ return nil unless assoc
113
+
114
+ target = assoc.klass
115
+ col = target.columns_hash[info[:original].to_s]
116
+ return nil unless col
117
+
118
+ prop.type = @config.type_mapping[col.type]
119
+ prop.multi = !!col.try(:array)
120
+ prop.nullable = col.null || info[:allow_nil]
121
+ prop
122
+ end
123
+
103
124
  def infer_types_for_attribute(prop)
104
125
  return nil unless model_class.respond_to?(:attribute_types)
105
126
 
@@ -111,9 +132,11 @@ module Typelizer
111
132
  end
112
133
 
113
134
  if attribute_type_obj.respond_to?(:subtype)
135
+ prop.column_type = attribute_type_obj.subtype.type
114
136
  prop.type = @config.type_mapping[attribute_type_obj.subtype.type]
115
137
  prop.multi = true
116
138
  elsif attribute_type_obj.respond_to?(:type)
139
+ prop.column_type = attribute_type_obj.type
117
140
  prop.type = @config.type_mapping[attribute_type_obj.type]
118
141
  end
119
142
 
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typelizer
4
+ class OpenAPI
5
+ SUPPORTED_VERSIONS = ["3.0", "3.1"].freeze
6
+ OPENAPI_TYPES = %i[integer number string boolean object array null].freeze
7
+
8
+ COLUMN_TYPE_MAP = {
9
+ integer: {type: :integer},
10
+ bigint: {type: :integer, format: :int64},
11
+ decimal: {type: :number, format: :double},
12
+ float: {type: :number, format: :float},
13
+ boolean: {type: :boolean},
14
+ string: {type: :string},
15
+ text: {type: :string},
16
+ citext: {type: :string},
17
+ uuid: {type: :string, format: :uuid},
18
+ date: {type: :string, format: :date},
19
+ datetime: {type: :string, format: :"date-time"},
20
+ time: {type: :string, format: :time},
21
+ json: {type: :object},
22
+ jsonb: {type: :object},
23
+ binary: {type: :string, format: :binary},
24
+ inet: {type: :string},
25
+ cidr: {type: :string}
26
+ }.freeze
27
+
28
+ def self.schema_for(interface, openapi_version: "3.0")
29
+ raise ArgumentError, "Unsupported openapi_version: #{openapi_version}. Must be one of: #{SUPPORTED_VERSIONS.join(", ")}" unless SUPPORTED_VERSIONS.include?(openapi_version.to_s)
30
+
31
+ required_props = interface.properties.reject(&:optional).map(&:name)
32
+ schema = {
33
+ type: :object,
34
+ properties: interface.properties.to_h { |prop| [prop.name, property_schema(prop, openapi_version: openapi_version)] }
35
+ }
36
+ schema[:required] = required_props if required_props.any?
37
+ schema
38
+ end
39
+
40
+ def self.property_schema(property, openapi_version: "3.0")
41
+ raise ArgumentError, "Unsupported openapi_version: #{openapi_version}. Must be one of: #{SUPPORTED_VERSIONS.join(", ")}" unless SUPPORTED_VERSIONS.include?(openapi_version.to_s)
42
+
43
+ definition = base_type(property)
44
+ ref = definition.delete("$ref")
45
+
46
+ definition = if ref
47
+ ref_schema(ref, property, openapi_version: openapi_version)
48
+ else
49
+ inline_schema(definition, property, openapi_version: openapi_version)
50
+ end
51
+
52
+ if property.multi
53
+ definition = {type: :array, items: definition}
54
+ if property.nullable
55
+ if openapi_version.to_s >= "3.1"
56
+ definition[:type] = [:array, :null]
57
+ else
58
+ definition[:nullable] = true
59
+ end
60
+ end
61
+ end
62
+
63
+ definition
64
+ end
65
+
66
+ def self.ref_schema(ref, property, openapi_version:)
67
+ has_siblings = property.nullable || property.comment.is_a?(String) || property.deprecated
68
+ ref_obj = {"$ref" => ref}
69
+
70
+ if openapi_version.to_s >= "3.1"
71
+ definition = property.nullable ? {oneOf: [ref_obj, {type: :null}]} : ref_obj
72
+ else
73
+ # In 3.0, $ref must stand alone — use allOf wrapper when siblings are needed
74
+ definition = has_siblings ? {allOf: [ref_obj]} : ref_obj
75
+ definition[:nullable] = true if property.nullable
76
+ end
77
+
78
+ definition[:description] = property.comment if property.comment.is_a?(String)
79
+ definition[:deprecated] = true if property.deprecated
80
+ definition
81
+ end
82
+
83
+ def self.inline_schema(definition, property, openapi_version:)
84
+ # For multi properties, nullable is applied to the array container in property_schema
85
+ unless property.multi
86
+ if property.nullable
87
+ if openapi_version.to_s >= "3.1"
88
+ definition[:type] = [definition[:type], :null]
89
+ else
90
+ definition[:nullable] = true
91
+ end
92
+ end
93
+ end
94
+ definition[:description] = property.comment if property.comment.is_a?(String)
95
+ if property.enum.is_a?(Array)
96
+ items_nullable = !property.multi && property.nullable
97
+ definition[:enum] = (items_nullable && !property.enum.include?(nil)) ? property.enum + [nil] : property.enum
98
+ end
99
+ definition[:deprecated] = true if property.deprecated
100
+ definition
101
+ end
102
+ private_class_method :ref_schema, :inline_schema
103
+
104
+ def self.base_type(property)
105
+ if property.type.respond_to?(:properties)
106
+ {"$ref" => "#/components/schemas/#{property.type.name}"}
107
+ elsif property.column_type && COLUMN_TYPE_MAP.key?(property.column_type)
108
+ result = COLUMN_TYPE_MAP[property.column_type].dup
109
+ result[:type] = :string if property.enum
110
+ result
111
+ elsif property.type.is_a?(String) && !OPENAPI_TYPES.include?(property.type.to_sym)
112
+ {"$ref" => "#/components/schemas/#{property.type}"}
113
+ else
114
+ type = property.type.to_s.to_sym
115
+ if OPENAPI_TYPES.include?(type)
116
+ {type: type}
117
+ else
118
+ {type: :object}
119
+ end
120
+ end
121
+ end
122
+ private_class_method :base_type
123
+ end
124
+ end
@@ -1,7 +1,7 @@
1
1
  module Typelizer
2
2
  Property = Struct.new(
3
3
  :name, :type, :optional, :nullable,
4
- :multi, :column_name, :comment, :enum, :enum_type_name, :deprecated,
4
+ :multi, :column_name, :column_type, :comment, :enum, :enum_type_name, :deprecated,
5
5
  :with_traits,
6
6
  keyword_init: true
7
7
  ) do
@@ -52,7 +52,8 @@ module Typelizer
52
52
  def fingerprint
53
53
  # Use array format for consistent output across Ruby versions
54
54
  # (Hash#inspect format changed in Ruby 3.4)
55
- to_h.merge(type: UnionTypeSorter.sort(type_name(sort_order: :alphabetical), :alphabetical))
55
+ # Exclude fields that do not affect generated TypeScript output
56
+ to_h.except(:column_type).merge(type: UnionTypeSorter.sort(type_name(sort_order: :alphabetical), :alphabetical))
56
57
  .to_a.inspect
57
58
  end
58
59
 
@@ -34,7 +34,7 @@ module Typelizer
34
34
  def infer_types(props, typelizes)
35
35
  props.map do |prop|
36
36
  # First check for typelize DSL in the trait
37
- dsl_type = typelizes[prop.column_name.to_sym]
37
+ dsl_type = typelizes[prop.column_name.to_sym] || typelizes[prop.name.to_sym]
38
38
  if dsl_type&.any?
39
39
  next prop.with(**dsl_type).tap do |property|
40
40
  property.comment ||= model_plugin.comment_for(property) if config.comments && property.comment != false
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Typelizer
4
- VERSION = "0.7.0"
4
+ VERSION = "0.8.0"
5
5
  end
data/lib/typelizer.rb CHANGED
@@ -16,6 +16,7 @@ require_relative "typelizer/import_sorter"
16
16
  require_relative "typelizer/interface"
17
17
  require_relative "typelizer/renderer"
18
18
  require_relative "typelizer/writer"
19
+ require_relative "typelizer/openapi"
19
20
  require_relative "typelizer/generator"
20
21
  require_relative "typelizer/type_parser"
21
22
  require_relative "typelizer/dsl"
@@ -62,8 +63,35 @@ module Typelizer
62
63
  yield configuration
63
64
  end
64
65
 
66
+ def interfaces(writer_name: nil)
67
+ load_serializers
68
+ context = WriterContext.new(writer_name: writer_name)
69
+ target_serializers(context.writer_config.reject_class)
70
+ .map { |klass| context.interface_for(klass) }
71
+ .reject(&:empty?)
72
+ end
73
+
74
+ def openapi_schemas(writer_name: nil, openapi_version: "3.0")
75
+ interfaces(writer_name: writer_name).to_h { |i| [i.name, OpenAPI.schema_for(i, openapi_version: openapi_version)] }
76
+ end
77
+
65
78
  private
66
79
 
80
+ def load_serializers
81
+ dirs.flat_map { |dir| Dir["#{dir}/**/*.rb"] }.each { |file| require file }
82
+ end
83
+
84
+ def target_serializers(reject_class)
85
+ resolved = base_classes.filter_map do |base_class|
86
+ Object.const_get(base_class) if Object.const_defined?(base_class)
87
+ end
88
+ raise ArgumentError, "No serializers found. Please ensure all your serializers include Typelizer::DSL." if base_classes.any? && resolved.none?
89
+
90
+ (resolved + resolved.flat_map(&:descendants)).uniq
91
+ .reject { |serializer| reject_class.call(serializer: serializer) }
92
+ .sort_by(&:name)
93
+ end
94
+
67
95
  attr_writer :base_classes
68
96
  end
69
97
 
@@ -72,3 +100,5 @@ module Typelizer
72
100
 
73
101
  self.base_classes = Set.new
74
102
  end
103
+
104
+ require_relative "typelizer/delegate_tracker"
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.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svyatoslav Kryukov
@@ -51,6 +51,7 @@ files:
51
51
  - lib/typelizer/config.rb
52
52
  - lib/typelizer/configuration.rb
53
53
  - lib/typelizer/contexts/writer_context.rb
54
+ - lib/typelizer/delegate_tracker.rb
54
55
  - lib/typelizer/dsl.rb
55
56
  - lib/typelizer/dsl/hooks.rb
56
57
  - lib/typelizer/dsl/hooks/alba.rb
@@ -64,6 +65,7 @@ files:
64
65
  - lib/typelizer/model_plugins/active_record.rb
65
66
  - lib/typelizer/model_plugins/auto.rb
66
67
  - lib/typelizer/model_plugins/poro.rb
68
+ - lib/typelizer/openapi.rb
67
69
  - lib/typelizer/property.rb
68
70
  - lib/typelizer/property_sorter.rb
69
71
  - lib/typelizer/railtie.rb