Module: TypeSpecFromSerializers

Defined in:
lib/typespec_from_serializers/generator.rb,
lib/typespec_from_serializers/rbi.rb,
lib/typespec_from_serializers/rdoc.rb,
lib/typespec_from_serializers/sorbet.rb,
lib/typespec_from_serializers/version.rb,
lib/typespec_from_serializers/dsl/routing.rb,
lib/typespec_from_serializers/dsl/controller.rb,
lib/typespec_from_serializers/dsl/serializer.rb,
lib/typespec_from_serializers/openapi_compiler.rb

Overview

Internal: A DSL to specify types for serializer attributes.

Defined Under Namespace

Modules: DSL, IO, Linting, MapperPatch, OpenAPICompiler, RBI, RDoc, ResourcesPatch, RoutePatch, RoutingPatchHelpers, SerializerRefinements, Sorbet Classes: Changes, Config, Interface, Operation, Property, Railtie, Resource, Runner, SerializerVisitor

Constant Summary collapse

VERSION =

Public: This library adheres to semantic versioning.

"0.5.4"
DEFAULT_TRANSFORM_KEYS =
->(key) { key.camelize(:lower).chomp("?") }
TYPESPEC_LANGUAGE_KEYWORDS =

TypeSpec language keywords that are always problematic

%w[
  using extends is import model scalar enum union interface
  namespace op alias true false null
].to_set.freeze
TYPESPEC_REFLECTION_TYPES =

TypeSpec Reflection types that conflict only at global scope When using a namespace, these names are safe (e.g., MyAPI.Model is OK)

%w[
  Model Scalar Enum Union Interface Operation Namespace
].to_set.freeze
REST_ACTIONS =

Rails RESTful resource action names

%w[index show create update destroy].freeze
MEMBER_ACTIONS =
%w[show update destroy].freeze
SPECIAL_ACTIONS =
%w[new edit].freeze

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.force_generationObject (readonly)

Returns the value of attribute force_generation.



610
611
612
# File 'lib/typespec_from_serializers/generator.rb', line 610

def force_generation
  @force_generation
end

Class Method Details

.configObject

Public: Configuration of the code generator.



613
614
615
616
617
# File 'lib/typespec_from_serializers/generator.rb', line 613

def config
  (@config ||= default_config(root)).tap do |config|
    yield(config) if block_given?
  end
end

.generate(force: ) ⇒ Object

Public: Generates code for all serializers in the app.



620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
# File 'lib/typespec_from_serializers/generator.rb', line 620

def generate(force: ENV["SERIALIZER_TYPESPEC_FORCE"])
  Linting.reset_count

  @force_generation = force
  clean_output_dir if force && config.output_dir.exist?

  if config.namespace
    load_serializers(all_serializer_files) if force
  else
    generate_index_file
  end

  controllers = generate_routes

  serializers = loaded_serializers
  serializers.each do |serializer|
    generate_model_for(serializer)
  end

  Linting.print_summary

  {serializers: serializers, controllers: controllers}
end

.generate_changedObject



644
645
646
647
648
649
650
651
# File 'lib/typespec_from_serializers/generator.rb', line 644

def generate_changed
  if changes.updated?
    config.output_dir.rmtree if changes.any_removed?
    load_serializers(changes.modified_files)
    generate
    changes.clear
  end
end

.generate_index_fileObject

Internal: Allows to import all serializer types from a single file.



667
668
669
670
671
672
673
# File 'lib/typespec_from_serializers/generator.rb', line 667

def generate_index_file
  cache_key = all_serializer_files.map { |file| file.delete_prefix(root.to_s) }.join
  write_if_changed(filename: "index", cache_key: cache_key) {
    load_serializers(all_serializer_files)
    serializers_index_content(loaded_serializers)
  }
end

.generate_model_for(serializer) ⇒ Object

Internal: Defines a TypeSpec model for the serializer.



654
655
656
657
658
659
660
661
662
663
664
# File 'lib/typespec_from_serializers/generator.rb', line 654

def generate_model_for(serializer)
  model = serializer.tsp_model

  write_if_changed(filename: "models/#{model.filename}", cache_key: model.inspect, extension: "tsp") {
    serializer_model_content(model)
  }
rescue => e
  $stderr.puts "ERROR in generate_model_for(#{serializer.name}): #{e.class}: #{e.message}"
  $stderr.puts e.backtrace.first(10).join("\n")
  raise
end

.generate_routesObject

Internal: Generates TypeSpec routes from Rails routes



676
677
678
679
680
681
682
683
684
685
686
687
# File 'lib/typespec_from_serializers/generator.rb', line 676

def generate_routes
  return [] unless defined?(Rails) && Rails.application

  routes, controllers = collect_rails_routes
  cache_key = routes.map(&:inspect).join
  write_if_changed(filename: "routes", cache_key: cache_key) {
    routes_content(routes)
  }

  # Return list of controller class names
  controllers.sort
end

.loaded_serializersObject

Public: Returns all loaded serializer classes.

Returns Array of serializer classes



724
725
726
727
728
729
730
731
732
733
# File 'lib/typespec_from_serializers/generator.rb', line 724

def loaded_serializers
  config.base_serializers.map(&:constantize)
    .flat_map(&:descendants)
    .uniq
    .reject { |s| s.name.nil? } # Filter out anonymous classes
    .sort_by(&:name)
    .reject { |s| skip_serializer?(s) }
rescue NameError
  raise ArgumentError, "Please ensure all your serializers extend BaseSerializer, or configure `config.base_serializers`."
end

.rbi_dirObject

Public: Returns the RBI base directory path.

Returns Pathname



717
718
719
# File 'lib/typespec_from_serializers/generator.rb', line 717

def rbi_dir
  root.join("sorbet/rbi")
end

.rootObject

Public: Returns the application root path.

Returns Pathname



710
711
712
# File 'lib/typespec_from_serializers/generator.rb', line 710

def root
  (defined?(Rails) && Rails.root) || Pathname.new(Dir.pwd)
end

.serializersObject

Public: Returns all loaded serializers.

Returns Array of serializer classes.



703
704
705
# File 'lib/typespec_from_serializers/generator.rb', line 703

def serializers
  loaded_serializers
end

.skip_serializer?(serializer) ⇒ Boolean

Internal: Checks if it should avoid generating an model.

Returns:

  • (Boolean)


690
691
692
693
# File 'lib/typespec_from_serializers/generator.rb', line 690

def skip_serializer?(serializer)
  serializer.name.in?(config.base_serializers) ||
    config.skip_serializer_if.call(serializer)
end

.track_changesObject

Internal: Returns an object compatible with FileUpdateChecker.



696
697
698
# File 'lib/typespec_from_serializers/generator.rb', line 696

def track_changes
  changes
end