├── .github └── workflows │ └── tests.yml ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── Guardfile ├── LICENSE ├── README.md ├── bin └── tapioca ├── examples └── sinatra-pet-shelter │ ├── Gemfile │ ├── Gemfile.lock │ ├── README.md │ ├── lib │ ├── app.rb │ ├── repository.rb │ └── resources │ │ └── dogs.rb │ ├── sorbet │ ├── config │ ├── rbi │ │ ├── annotations │ │ │ ├── .gitattributes │ │ │ ├── activesupport.rbi │ │ │ └── minitest.rbi │ │ ├── dsl │ │ │ ├── .gitattributes │ │ │ └── active_support │ │ │ │ └── callbacks.rbi │ │ └── gems │ │ │ ├── .gitattributes │ │ │ ├── activesupport@8.0.0.rbi │ │ │ ├── base64@0.2.0.rbi │ │ │ ├── benchmark@0.4.0.rbi │ │ │ ├── bigdecimal@3.1.8.rbi │ │ │ ├── coderay@1.1.3.rbi │ │ │ ├── concurrent-ruby@1.3.4.rbi │ │ │ ├── connection_pool@2.4.1.rbi │ │ │ ├── diff-lcs@1.5.1.rbi │ │ │ ├── drb@2.2.1.rbi │ │ │ ├── erubi@1.13.0.rbi │ │ │ ├── i18n@1.14.6.rbi │ │ │ ├── json@2.7.2.rbi │ │ │ ├── logger@1.6.1.rbi │ │ │ ├── method_source@1.1.0.rbi │ │ │ ├── minitest@5.25.2.rbi │ │ │ ├── multi_json@1.15.0.rbi │ │ │ ├── mustermann@3.0.3.rbi │ │ │ ├── netrc@0.11.0.rbi │ │ │ ├── parallel@1.26.3.rbi │ │ │ ├── polyfill@1.9.0.rbi │ │ │ ├── prism@0.30.0.rbi │ │ │ ├── pry@0.14.2.rbi │ │ │ ├── rack-protection@3.2.0.rbi │ │ │ ├── rack-test@2.1.0.rbi │ │ │ ├── rack@2.2.9.rbi │ │ │ ├── rbi@0.1.14.rbi │ │ │ ├── resource_registry@0.0.0-61b71b89d828ae8b3da80256be9e5b817b48a49c.rbi │ │ │ ├── rspec-core@3.13.1.rbi │ │ │ ├── rspec-expectations@3.13.3.rbi │ │ │ ├── rspec-mocks@3.13.1.rbi │ │ │ ├── rspec-support@3.13.1.rbi │ │ │ ├── rspec@3.13.0.rbi │ │ │ ├── ruby2_keywords@0.0.5.rbi │ │ │ ├── safe_type@1.1.1.rbi │ │ │ ├── securerandom@0.3.2.rbi │ │ │ ├── sinatra-contrib@3.2.0.rbi │ │ │ ├── sinatra@3.2.0.rbi │ │ │ ├── sorbet-coerce@0.7.0.rbi │ │ │ ├── spoom@1.5.0.rbi │ │ │ ├── tapioca@0.15.1.rbi │ │ │ ├── thor@1.3.2.rbi │ │ │ ├── tilt@2.4.0.rbi │ │ │ ├── tzinfo@2.0.6.rbi │ │ │ ├── uri@1.0.2.rbi │ │ │ ├── yard-sorbet@0.9.0.rbi │ │ │ └── yard@0.9.37.rbi │ └── tapioca │ │ ├── config.yml │ │ └── require.rb │ └── spec │ ├── app_spec.rb │ └── spec_helper.rb ├── lib ├── README.md ├── package.yml ├── package_todo.yml ├── resource_definition.schema.json ├── resource_registry.rb ├── resource_registry │ ├── capabilities │ │ └── capability_config.rb │ ├── capability_factory.rb │ ├── configuration.rb │ ├── entity_finder.rb │ ├── load_resources_from_cache.rb │ ├── registry.rb │ ├── relationship.rb │ ├── relationship_type.rb │ ├── relationship_type_factory.rb │ ├── relationship_types │ │ ├── belongs_to.rb │ │ ├── has_many.rb │ │ ├── has_many_through.rb │ │ └── has_one.rb │ ├── repositories │ │ └── base.rb │ ├── resource.rb │ ├── serializer.rb │ ├── verb.rb │ ├── versions.rb │ └── versions │ │ └── version.rb ├── runtime_generic.rb └── schema_registry │ ├── README.md │ ├── filter_field.rb │ ├── json_schema_mapper.rb │ ├── maybe.rb │ ├── maybe │ ├── absent.rb │ └── present.rb │ ├── property.rb │ ├── property_mapper.rb │ ├── property_type.rb │ ├── registry.rb │ └── schema.rb ├── rbi └── resource_registry.rbi ├── resource_registry.gemspec ├── sorbet ├── config ├── rbi │ ├── annotations │ │ ├── .gitattributes │ │ ├── activesupport.rbi │ │ └── minitest.rbi │ ├── dsl │ │ ├── .gitattributes │ │ └── active_support │ │ │ └── callbacks.rbi │ ├── gems │ │ ├── .gitattributes │ │ ├── activesupport.rbi │ │ ├── activesupport@7.2.2.rbi │ │ ├── base64@0.2.0.rbi │ │ ├── benchmark@0.4.0.rbi │ │ ├── bigdecimal.rbi │ │ ├── bigdecimal@3.1.8.rbi │ │ ├── coderay@1.1.3.rbi │ │ ├── concurrent-ruby.rbi │ │ ├── concurrent-ruby@1.3.4.rbi │ │ ├── connection_pool.rbi │ │ ├── connection_pool@2.4.1.rbi │ │ ├── diff-lcs@1.5.1.rbi │ │ ├── drb.rbi │ │ ├── drb@2.2.1.rbi │ │ ├── erubi@1.13.1.rbi │ │ ├── ffi@1.17.0.rbi │ │ ├── formatador@1.1.0.rbi │ │ ├── guard-compat@1.2.1.rbi │ │ ├── guard-rspec@4.7.3.rbi │ │ ├── guard@2.18.1.rbi │ │ ├── i18n.rbi │ │ ├── i18n@1.14.6.rbi │ │ ├── listen@3.9.0.rbi │ │ ├── logger@1.6.1.rbi │ │ ├── lumberjack@1.2.10.rbi │ │ ├── method_source@1.1.0.rbi │ │ ├── minitest.rbi │ │ ├── minitest@5.25.2.rbi │ │ ├── nenv@0.3.0.rbi │ │ ├── netrc@0.11.0.rbi │ │ ├── notiffany@0.1.3.rbi │ │ ├── parallel@1.26.3.rbi │ │ ├── polyfill.rbi │ │ ├── polyfill@1.9.0.rbi │ │ ├── prettier_print@1.2.1.rbi │ │ ├── prism@1.3.0.rbi │ │ ├── pry@0.14.2.rbi │ │ ├── rb-fsevent@0.11.2.rbi │ │ ├── rb-inotify@0.11.1.rbi │ │ ├── rbi@0.2.4.rbi │ │ ├── rspec-core.rbi │ │ ├── rspec-core@3.13.0.rbi │ │ ├── rspec-expectations.rbi │ │ ├── rspec-expectations@3.13.1.rbi │ │ ├── rspec-json_expectations@2.2.0.rbi │ │ ├── rspec-mocks.rbi │ │ ├── rspec-mocks@3.13.1.rbi │ │ ├── rspec-sorbet@1.9.2.rbi │ │ ├── rspec-support.rbi │ │ ├── rspec-support@3.13.1.rbi │ │ ├── rspec.rbi │ │ ├── rspec@3.13.0.rbi │ │ ├── safe_type.rbi │ │ ├── safe_type@1.1.1.rbi │ │ ├── securerandom@0.3.2.rbi │ │ ├── shellany@0.0.1.rbi │ │ ├── sorbet-coerce.rbi │ │ ├── sorbet-coerce@0.7.0.rbi │ │ ├── spoom@1.5.4.rbi │ │ ├── syntax_tree@6.2.0.rbi │ │ ├── tapioca@0.16.11.rbi │ │ ├── thor@1.3.0.rbi │ │ ├── tzinfo.rbi │ │ ├── tzinfo@2.0.6.rbi │ │ ├── uri@1.0.2.rbi │ │ ├── yard-sorbet@0.9.0.rbi │ │ └── yard@0.9.37.rbi │ └── shims │ │ ├── rails.rbi │ │ └── repositories.rbi └── tapioca │ ├── config.yml │ └── require.rb └── spec ├── capability_factory_spec.rb ├── dummy_capability.rb ├── dummy_repo.rb ├── resource_registry ├── capabilities │ └── capability_config_spec.rb ├── registry_spec.rb ├── relationship_spec.rb ├── resource_spec.rb └── versions_spec.rb ├── schema_registry └── maybe_spec.rb ├── spec_helper.rb └── void_capability.rb /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | on: 3 | push: 4 | branches: 5 | - "**" # matches every branch 6 | jobs: 7 | run-rspec-tests: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Set up Ruby 12 | uses: ruby/setup-ruby@v1 13 | with: 14 | # Not needed with a .ruby-version file 15 | ruby-version: 3.3.3 16 | # runs 'bundle install' and caches installed gems automatically 17 | bundler-cache: true 18 | - name: Run sorbet typechecks 19 | run: | 20 | bundle exec srb tc --ignore examples 21 | - name: Run rspec tests 22 | run: | 23 | bundle exec rspec 24 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.3.3 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "activesupport" 6 | gem "rspec" 7 | gem "rspec-json_expectations" 8 | gem "rspec-sorbet", "~> 1.9.2" 9 | gem "sorbet-coerce", "~> 0.7" 10 | gem "sorbet-static-and-runtime", "0.5.11845" 11 | gem "tapioca", "~> 0.16.11", require: false 12 | gem "syntax_tree", "~> 6.2", require: false 13 | 14 | gem "guard" 15 | gem "guard-rspec" 16 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (7.2.2) 5 | base64 6 | benchmark (>= 0.3) 7 | bigdecimal 8 | concurrent-ruby (~> 1.0, >= 1.3.1) 9 | connection_pool (>= 2.2.5) 10 | drb 11 | i18n (>= 1.6, < 2) 12 | logger (>= 1.4.2) 13 | minitest (>= 5.1) 14 | securerandom (>= 0.3) 15 | tzinfo (~> 2.0, >= 2.0.5) 16 | uri (>= 0.13.1) 17 | base64 (0.2.0) 18 | benchmark (0.4.0) 19 | bigdecimal (3.1.8) 20 | coderay (1.1.3) 21 | concurrent-ruby (1.3.4) 22 | connection_pool (2.4.1) 23 | diff-lcs (1.5.1) 24 | drb (2.2.1) 25 | erubi (1.13.1) 26 | ffi (1.17.0-arm64-darwin) 27 | ffi (1.17.0-x86_64-linux-gnu) 28 | formatador (1.1.0) 29 | guard (2.18.1) 30 | formatador (>= 0.2.4) 31 | listen (>= 2.7, < 4.0) 32 | lumberjack (>= 1.0.12, < 2.0) 33 | nenv (~> 0.1) 34 | notiffany (~> 0.0) 35 | pry (>= 0.13.0) 36 | shellany (~> 0.0) 37 | thor (>= 0.18.1) 38 | guard-compat (1.2.1) 39 | guard-rspec (4.7.3) 40 | guard (~> 2.1) 41 | guard-compat (~> 1.1) 42 | rspec (>= 2.99.0, < 4.0) 43 | i18n (1.14.6) 44 | concurrent-ruby (~> 1.0) 45 | listen (3.9.0) 46 | rb-fsevent (~> 0.10, >= 0.10.3) 47 | rb-inotify (~> 0.9, >= 0.9.10) 48 | logger (1.6.1) 49 | lumberjack (1.2.10) 50 | method_source (1.1.0) 51 | minitest (5.25.2) 52 | nenv (0.3.0) 53 | netrc (0.11.0) 54 | notiffany (0.1.3) 55 | nenv (~> 0.1) 56 | shellany (~> 0.0) 57 | parallel (1.26.3) 58 | polyfill (1.9.0) 59 | prettier_print (1.2.1) 60 | prism (1.3.0) 61 | pry (0.14.2) 62 | coderay (~> 1.1) 63 | method_source (~> 1.0) 64 | rb-fsevent (0.11.2) 65 | rb-inotify (0.11.1) 66 | ffi (~> 1.0) 67 | rbi (0.2.4) 68 | prism (~> 1.0) 69 | sorbet-runtime (>= 0.5.9204) 70 | rspec (3.13.0) 71 | rspec-core (~> 3.13.0) 72 | rspec-expectations (~> 3.13.0) 73 | rspec-mocks (~> 3.13.0) 74 | rspec-core (3.13.0) 75 | rspec-support (~> 3.13.0) 76 | rspec-expectations (3.13.1) 77 | diff-lcs (>= 1.2.0, < 2.0) 78 | rspec-support (~> 3.13.0) 79 | rspec-json_expectations (2.2.0) 80 | rspec-mocks (3.13.1) 81 | diff-lcs (>= 1.2.0, < 2.0) 82 | rspec-support (~> 3.13.0) 83 | rspec-sorbet (1.9.2) 84 | sorbet-runtime 85 | rspec-support (3.13.1) 86 | safe_type (1.1.1) 87 | securerandom (0.3.2) 88 | shellany (0.0.1) 89 | sorbet (0.5.11845) 90 | sorbet-static (= 0.5.11845) 91 | sorbet-coerce (0.7.0) 92 | polyfill (~> 1.8) 93 | safe_type (~> 1.1, >= 1.1.1) 94 | sorbet-runtime (>= 0.4.4704) 95 | sorbet-runtime (0.5.11845) 96 | sorbet-static (0.5.11845-universal-darwin) 97 | sorbet-static (0.5.11845-x86_64-linux) 98 | sorbet-static-and-runtime (0.5.11845) 99 | sorbet (= 0.5.11845) 100 | sorbet-runtime (= 0.5.11845) 101 | spoom (1.5.4) 102 | erubi (>= 1.10.0) 103 | prism (>= 0.28.0) 104 | rbi (>= 0.2.3) 105 | sorbet-static-and-runtime (>= 0.5.10187) 106 | thor (>= 0.19.2) 107 | syntax_tree (6.2.0) 108 | prettier_print (>= 1.2.0) 109 | tapioca (0.16.11) 110 | benchmark 111 | bundler (>= 2.2.25) 112 | netrc (>= 0.11.0) 113 | parallel (>= 1.21.0) 114 | rbi (~> 0.2) 115 | sorbet-static-and-runtime (>= 0.5.11087) 116 | spoom (>= 1.2.0) 117 | thor (>= 1.2.0) 118 | yard-sorbet 119 | thor (1.3.0) 120 | tzinfo (2.0.6) 121 | concurrent-ruby (~> 1.0) 122 | uri (1.0.2) 123 | yard (0.9.37) 124 | yard-sorbet (0.9.0) 125 | sorbet-runtime 126 | yard 127 | 128 | PLATFORMS 129 | arm64-darwin-23 130 | x86_64-linux 131 | 132 | DEPENDENCIES 133 | activesupport 134 | guard 135 | guard-rspec 136 | rspec 137 | rspec-json_expectations 138 | rspec-sorbet (~> 1.9.2) 139 | sorbet-coerce (~> 0.7) 140 | sorbet-static-and-runtime (= 0.5.11845) 141 | syntax_tree (~> 6.2) 142 | tapioca (~> 0.16.11) 143 | 144 | BUNDLED WITH 145 | 2.5.11 146 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | ## Uncomment and set this to only include directories you want to watch 5 | # directories %w(app lib config test spec features) \ 6 | # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")} 7 | 8 | ## Note: if you are using the `directories` clause above and you are not 9 | ## watching the project directory ('.'), then you will want to move 10 | ## the Guardfile to a watched dir and symlink it back, e.g. 11 | # 12 | # $ mkdir config 13 | # $ mv Guardfile config/ 14 | # $ ln -s config/Guardfile . 15 | # 16 | # and, you'll have to watch "config/Guardfile" instead of "Guardfile" 17 | 18 | # Note: The cmd option is now required due to the increasing number of ways 19 | # rspec may be run, below are examples of the most common uses. 20 | # * bundler: 'bundle exec rspec' 21 | # * bundler binstubs: 'bin/rspec' 22 | # * spring: 'bin/rspec' (This will use spring if running and you have 23 | # installed the spring binstubs per the docs) 24 | # * zeus: 'zeus rspec' (requires the server to be started separately) 25 | # * 'just' rspec: 'rspec' 26 | 27 | guard :rspec, cmd: "bundle exec rspec" do 28 | require "guard/rspec/dsl" 29 | dsl = Guard::RSpec::Dsl.new(self) 30 | 31 | # Feel free to open issues for suggestions and improvements 32 | 33 | # RSpec files 34 | rspec = dsl.rspec 35 | watch(rspec.spec_helper) { rspec.spec_dir } 36 | watch(rspec.spec_support) { rspec.spec_dir } 37 | watch(rspec.spec_files) 38 | 39 | # Ruby files 40 | ruby = dsl.ruby 41 | dsl.watch_spec_files_for(ruby.lib_files) 42 | 43 | # Rails files 44 | rails = dsl.rails(view_extensions: %w(erb haml slim)) 45 | dsl.watch_spec_files_for(rails.app_files) 46 | dsl.watch_spec_files_for(rails.views) 47 | 48 | watch(rails.controllers) do |m| 49 | [ 50 | rspec.spec.call("routing/#{m[1]}_routing"), 51 | rspec.spec.call("controllers/#{m[1]}_controller"), 52 | rspec.spec.call("acceptance/#{m[1]}") 53 | ] 54 | end 55 | 56 | # Rails config changes 57 | watch(rails.spec_helper) { rspec.spec_dir } 58 | watch(rails.routes) { "#{rspec.spec_dir}/routing" } 59 | watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" } 60 | 61 | # Capybara features specs 62 | watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") } 63 | watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") } 64 | 65 | # Turnip features and steps 66 | watch(%r{^spec/acceptance/(.+)\.feature$}) 67 | watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m| 68 | Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance" 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Factorial 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Resource Registry 2 | 3 | A declarative resource-oriented registry for a generic usage. 4 | 5 | ## What is this? 6 | 7 | Resource Registry allows you to define resources and their actions in a 8 | declarative way. Leaving the imperative implementation of the business logic 9 | behavior to the user. (Using the repository pattern). And allowing to create 10 | generic features for the whole catalog of resources at once. 11 | 12 | This is very useful to scale big projects codebases in order to implement 13 | generic features for the whole catalog or resources. 14 | 15 | Resource Registry defines a few basic concepts to build this catalog. 16 | 17 | ### Anatomy of a resource, `ResourceRegistry::Resource` 18 | 19 | Resource represent the central part of this library. They should contain and 20 | provide all the necessary information to generate features. This includes 21 | Capabilities, Repository, verbs and Entity/DTOs schemas. 22 | 23 | An example of a resource: 24 | 25 | ```ruby 26 | class GraphqlCapability < T::Struct 27 | extend T::Sig 28 | 29 | include ResourceRegistry::Capabilities::CapabilityConfig 30 | 31 | sig { override.returns(Symbol) } 32 | def self.key 33 | :void_capability 34 | end 35 | end 36 | 37 | ResourceRegistry::Resource.new( 38 | repository_raw: YourRepositoryClass.to_s, 39 | capabilities: { 40 | graphql: GraphqlCapability.new 41 | }, 42 | verbs: { 43 | read: ResourceRegistry::Verb.new( 44 | id: verb, 45 | dto_raw: dto_klass.to_s, 46 | schema: read_verb_schema, 47 | return_many: true 48 | ) 49 | }, 50 | schema: SchemaRegistry::Schema.new( 51 | name: 'employees', 52 | namespace: 'employees', 53 | properties: [ 54 | SchemaRegistry::Property.new( 55 | name: 'id', 56 | types: [SchemaRegistry::PropertyType::String], 57 | required: true 58 | ), 59 | SchemaRegistry::Property.new( 60 | name: 'fullName', 61 | types: [SchemaRegistry::PropertyType::String], 62 | required: true 63 | ) 64 | ] 65 | ) 66 | ) 67 | ``` 68 | 69 | ### The registry itself, `ResourceRegistry::Registry` 70 | 71 | Gives you access to the whole library using the following API: 72 | 73 | ```ruby 74 | registry = ResourceRegistry::Registry.new 75 | 76 | # Fetch a resource by its identifier 77 | registry.fetch(:employees) 78 | 79 | # Fetch all resources 80 | registry.fetch_rall 81 | ``` 82 | 83 | ## What does this gem bring to the table? 84 | 85 | - Schema registry for resources, maybe we can infere them from entities 86 | - Relate events to resources actions (CRUD and not CRUD) 87 | 88 | ## Install 89 | 90 | Add the following lines in your Gemfile: 91 | 92 | ```ruby 93 | gem 'resource_registry', github: 'factorialco/resource-registry' 94 | ``` 95 | 96 | And run `bundle install` 97 | 98 | ## Setting up your first resources 99 | 100 | - [Check this project to see an example Sinatra based app using Resource Registry](examples/sinatra-pet-shelter) 101 | 102 | ## Similar projects 103 | 104 | Check [ash](https://ash-hq.org/) for a similar and much mature approach applied to Elixir apps. 105 | -------------------------------------------------------------------------------- /bin/tapioca: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'tapioca' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("tapioca", "tapioca") 28 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "json" 6 | gem "resource_registry", github: "factorialco/resource-registry", branch: "main" 7 | gem "sinatra" 8 | gem "sinatra-contrib" 9 | gem "tapioca", "~> 0.15.1", require: false 10 | 11 | group :development, :test do 12 | gem "pry" 13 | gem "rack-test" 14 | gem "rspec" 15 | end 16 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/factorialco/resource-registry.git 3 | revision: 61b71b89d828ae8b3da80256be9e5b817b48a49c 4 | branch: chore/update-sorbet-version-and-fixes 5 | specs: 6 | resource_registry (0.0.0) 7 | activesupport (>= 7.1.3) 8 | sorbet-coerce (~> 0.7) 9 | sorbet-runtime (= 0.5.11670) 10 | 11 | GEM 12 | remote: https://rubygems.org/ 13 | specs: 14 | activesupport (8.0.0) 15 | base64 16 | benchmark (>= 0.3) 17 | bigdecimal 18 | concurrent-ruby (~> 1.0, >= 1.3.1) 19 | connection_pool (>= 2.2.5) 20 | drb 21 | i18n (>= 1.6, < 2) 22 | logger (>= 1.4.2) 23 | minitest (>= 5.1) 24 | securerandom (>= 0.3) 25 | tzinfo (~> 2.0, >= 2.0.5) 26 | uri (>= 0.13.1) 27 | base64 (0.2.0) 28 | benchmark (0.4.0) 29 | bigdecimal (3.1.8) 30 | coderay (1.1.3) 31 | concurrent-ruby (1.3.4) 32 | connection_pool (2.4.1) 33 | diff-lcs (1.5.1) 34 | drb (2.2.1) 35 | erubi (1.13.0) 36 | i18n (1.14.6) 37 | concurrent-ruby (~> 1.0) 38 | json (2.7.2) 39 | logger (1.6.1) 40 | method_source (1.1.0) 41 | minitest (5.25.2) 42 | multi_json (1.15.0) 43 | mustermann (3.0.3) 44 | ruby2_keywords (~> 0.0.1) 45 | netrc (0.11.0) 46 | parallel (1.26.3) 47 | polyfill (1.9.0) 48 | prism (0.30.0) 49 | pry (0.14.2) 50 | coderay (~> 1.1) 51 | method_source (~> 1.0) 52 | rack (2.2.9) 53 | rack-protection (3.2.0) 54 | base64 (>= 0.1.0) 55 | rack (~> 2.2, >= 2.2.4) 56 | rack-test (2.1.0) 57 | rack (>= 1.3) 58 | rbi (0.1.14) 59 | prism (>= 0.18.0, < 1.0.0) 60 | sorbet-runtime (>= 0.5.9204) 61 | rspec (3.13.0) 62 | rspec-core (~> 3.13.0) 63 | rspec-expectations (~> 3.13.0) 64 | rspec-mocks (~> 3.13.0) 65 | rspec-core (3.13.1) 66 | rspec-support (~> 3.13.0) 67 | rspec-expectations (3.13.3) 68 | diff-lcs (>= 1.2.0, < 2.0) 69 | rspec-support (~> 3.13.0) 70 | rspec-mocks (3.13.1) 71 | diff-lcs (>= 1.2.0, < 2.0) 72 | rspec-support (~> 3.13.0) 73 | rspec-support (3.13.1) 74 | ruby2_keywords (0.0.5) 75 | safe_type (1.1.1) 76 | securerandom (0.3.2) 77 | sinatra (3.2.0) 78 | mustermann (~> 3.0) 79 | rack (~> 2.2, >= 2.2.4) 80 | rack-protection (= 3.2.0) 81 | tilt (~> 2.0) 82 | sinatra-contrib (3.2.0) 83 | multi_json (>= 0.0.2) 84 | mustermann (~> 3.0) 85 | rack-protection (= 3.2.0) 86 | sinatra (= 3.2.0) 87 | tilt (~> 2.0) 88 | sorbet (0.5.11670) 89 | sorbet-static (= 0.5.11670) 90 | sorbet-coerce (0.7.0) 91 | polyfill (~> 1.8) 92 | safe_type (~> 1.1, >= 1.1.1) 93 | sorbet-runtime (>= 0.4.4704) 94 | sorbet-runtime (0.5.11670) 95 | sorbet-static (0.5.11670-universal-darwin) 96 | sorbet-static (0.5.11670-x86_64-linux) 97 | sorbet-static-and-runtime (0.5.11670) 98 | sorbet (= 0.5.11670) 99 | sorbet-runtime (= 0.5.11670) 100 | spoom (1.5.0) 101 | erubi (>= 1.10.0) 102 | prism (>= 0.28.0) 103 | sorbet-static-and-runtime (>= 0.5.10187) 104 | thor (>= 0.19.2) 105 | tapioca (0.15.1) 106 | bundler (>= 2.2.25) 107 | netrc (>= 0.11.0) 108 | parallel (>= 1.21.0) 109 | rbi (>= 0.1.4, < 0.2) 110 | sorbet-static-and-runtime (>= 0.5.11087) 111 | spoom (>= 1.2.0) 112 | thor (>= 1.2.0) 113 | yard-sorbet 114 | thor (1.3.2) 115 | tilt (2.4.0) 116 | tzinfo (2.0.6) 117 | concurrent-ruby (~> 1.0) 118 | uri (1.0.2) 119 | yard (0.9.37) 120 | yard-sorbet (0.9.0) 121 | sorbet-runtime 122 | yard 123 | 124 | PLATFORMS 125 | arm64-darwin-23 126 | x86_64-linux 127 | 128 | DEPENDENCIES 129 | json 130 | pry 131 | rack-test 132 | resource_registry! 133 | rspec 134 | sinatra 135 | sinatra-contrib 136 | tapioca (~> 0.15.1) 137 | 138 | BUNDLED WITH 139 | 2.4.7 140 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/README.md: -------------------------------------------------------------------------------- 1 | # Pet shelter example application 2 | 3 | This is just an example API to show up how to use Resource Registry in a Ruby application. 4 | 5 | ## Start server 6 | 7 | In order to start the server, run the following command: 8 | 9 | ```bash 10 | ruby app.rb 11 | ``` 12 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/lib/app.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "sinatra" 4 | require "sinatra/multi_route" 5 | require "resource_registry" 6 | 7 | require "sinatra/reloader" if development? 8 | require "pry" 9 | 10 | require_relative "repository" 11 | 12 | Dir[File.expand_path("lib/resources/*.rb")].each { |f| require_relative(f) } 13 | 14 | # Main entrypoint of the application 15 | class App < Sinatra::Base 16 | register Sinatra::MultiRoute 17 | 18 | registry, = 19 | ResourceRegistry::Initializer.new(repository_base_klass: Repository).call 20 | resources = registry.fetch_all 21 | 22 | resources.each do |id, resource| 23 | next unless resource.verbs.include?(:read) 24 | 25 | route :get, "/#{resource.collection_name}" do 26 | content_type :json 27 | { data: [] }.to_json 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/lib/repository.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: true 3 | 4 | class Repository 5 | extend T::Sig 6 | 7 | include ::ResourceRegistry::Repositories::Base 8 | extend RuntimeGeneric 9 | 10 | Entity = type_member { { upper: T::Struct } } 11 | 12 | sig { returns(T.untyped) } 13 | def self.entity 14 | # Dirty hack to make Tapioca work with `tapioca dsl` command, our 15 | # RuntimeGeneric is not compatible with it 16 | return if defined?(Tapioca) 17 | 18 | T.unsafe(const_get(:Entity)).inner_type[:fixed] 19 | end 20 | 21 | # Migrate to RR gem 22 | # FIXME: Review 23 | sig(:final) { returns(String) } 24 | def self.namespace 25 | "" 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/lib/resources/dogs.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | # frozen_string_literal: true 3 | 4 | require_relative "../repository" 5 | 6 | class ReadDto < T::Struct 7 | const :id, String 8 | end 9 | 10 | class Dog < T::Struct 11 | const :id, String 12 | end 13 | 14 | class Dogs < Repository 15 | extend T::Sig 16 | 17 | Entity = type_member { { fixed: Dog } } 18 | 19 | # FIXME: Review `context` 20 | sig do 21 | override.params(dto: ReadDto, context: T.untyped).returns(T::Array[Dog]) 22 | end 23 | def read(dto:, context:) 24 | [] 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/config: -------------------------------------------------------------------------------- 1 | --dir 2 | . 3 | --ignore=/tmp/ 4 | --ignore=/vendor/bundle 5 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/rbi/annotations/.gitattributes: -------------------------------------------------------------------------------- 1 | **/*.rbi linguist-vendored=true 2 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/rbi/annotations/minitest.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This file was pulled from a central RBI files repository. 5 | # Please run `bin/tapioca annotations` to update it. 6 | 7 | module Minitest::Assertions 8 | sig { params(test: T.anything, msg: T.anything).returns(TrueClass) } 9 | def assert(test, msg = nil); end 10 | 11 | sig { params(obj: T.anything, msg: T.anything).returns(TrueClass) } 12 | def assert_empty(obj, msg = nil); end 13 | 14 | sig { params(exp: T.anything, act: T.anything, msg: T.anything).returns(TrueClass) } 15 | def assert_equal(exp, act, msg = nil); end 16 | 17 | sig { params(exp: T.anything, act: T.anything, delta: Numeric, msg: T.anything).returns(TrueClass) } 18 | def assert_in_delta(exp, act, delta = T.unsafe(nil), msg = nil); end 19 | 20 | sig { params(a: T.anything, b: T.anything, epsilon: Numeric, msg: T.anything).returns(TrueClass) } 21 | def assert_in_epsilon(a, b, epsilon = T.unsafe(nil), msg = nil); end 22 | 23 | sig { params(collection: T.anything, obj: T.anything, msg: T.anything).returns(TrueClass) } 24 | def assert_includes(collection, obj, msg = nil); end 25 | 26 | sig { params(cls: T.anything, obj: T.anything, msg: T.anything).returns(TrueClass) } 27 | def assert_instance_of(cls, obj, msg = nil); end 28 | 29 | sig { params(cls: T.anything, obj: T.anything, msg: T.anything).returns(TrueClass) } 30 | def assert_kind_of(cls, obj, msg = nil); end 31 | 32 | sig { params(matcher: T.any(String, Regexp), obj: T.anything, msg: T.anything).returns(MatchData) } 33 | def assert_match(matcher, obj, msg = nil); end 34 | 35 | sig { params(obj: T.anything, msg: T.anything).returns(TrueClass) } 36 | def assert_nil(obj, msg = nil); end 37 | 38 | sig { params(o1: T.anything, op: T.any(Symbol, String), o2: T.anything, msg: T.anything).returns(TrueClass) } 39 | def assert_operator(o1, op, o2 = T.unsafe(nil), msg = nil); end 40 | 41 | sig { params(stdout: T.nilable(T.any(String, Regexp)), stderr: T.nilable(T.any(String, Regexp)), block: T.proc.void).returns(T::Boolean) } 42 | def assert_output(stdout = nil, stderr = nil, &block); end 43 | 44 | sig { params(path: T.any(String, Pathname), msg: T.anything).returns(TrueClass) } 45 | def assert_path_exists(path, msg = nil); end 46 | 47 | sig { params(block: T.proc.void).returns(TrueClass) } 48 | def assert_pattern(&block); end 49 | 50 | sig { params(o1: T.anything, op: T.any(String, Symbol), msg: T.anything).returns(TrueClass) } 51 | def assert_predicate(o1, op, msg = nil); end 52 | 53 | sig { params(exp: NilClass, block: T.proc.void).returns(StandardError) } 54 | sig { type_parameters(:T).params(exp: T.any(T::Class[T.type_parameter(:T)], Regexp, String), block: T.proc.void).returns(T.type_parameter(:T)) } 55 | def assert_raises(*exp, &block); end 56 | 57 | sig { params(obj: T.anything, meth: T.any(String, Symbol), msg: T.anything, include_all: T::Boolean).returns(TrueClass) } 58 | def assert_respond_to(obj, meth, msg = nil, include_all: false); end 59 | 60 | sig { params(exp: T.anything, act: T.anything, msg: T.anything).returns(TrueClass) } 61 | def assert_same(exp, act, msg = nil); end 62 | 63 | sig { params(send_ary: T::Array[T.anything], m: T.anything).returns(T::Boolean) } 64 | def assert_send(send_ary, m = nil); end 65 | 66 | sig { params(block: T.proc.void).returns(T::Boolean) } 67 | def assert_silent(&block); end 68 | 69 | sig { params(sym: Symbol, msg: T.anything, block: T.proc.void).returns(T.anything) } 70 | def assert_throws(sym, msg = nil, &block); end 71 | 72 | sig { params(test: T.anything, msg: T.anything).returns(TrueClass) } 73 | def refute(test, msg = nil); end 74 | 75 | sig { params(obj: T.anything, msg: T.anything).returns(TrueClass) } 76 | def refute_empty(obj, msg = nil); end 77 | 78 | sig { params(exp: T.anything, act: T.anything, msg: T.anything).returns(TrueClass) } 79 | def refute_equal(exp, act, msg = nil); end 80 | 81 | sig { params(exp: T.anything, act: T.anything, delta: Numeric, msg: T.anything).returns(TrueClass) } 82 | def refute_in_delta(exp, act, delta = T.unsafe(nil), msg = nil); end 83 | 84 | sig { params(a: T.anything, b: T.anything, epsilon: Numeric, msg: T.anything).returns(TrueClass) } 85 | def refute_in_epsilon(a, b, epsilon = T.unsafe(nil), msg = nil); end 86 | 87 | sig { params(collection: T.anything, obj: T.anything, msg: T.anything).returns(TrueClass) } 88 | def refute_includes(collection, obj, msg = nil); end 89 | 90 | sig { params(cls: T.anything, obj: T.anything, msg: T.anything).returns(TrueClass) } 91 | def refute_instance_of(cls, obj, msg = nil); end 92 | 93 | sig { params(cls: T.anything, obj: T.anything, msg: T.anything).returns(TrueClass) } 94 | def refute_kind_of(cls, obj, msg = nil); end 95 | 96 | sig { params(matcher: T.any(String, Regexp), obj: T.anything, msg: T.anything).returns(TrueClass) } 97 | def refute_match(matcher, obj, msg = nil); end 98 | 99 | sig { params(obj: T.anything, msg: T.anything).returns(TrueClass) } 100 | def refute_nil(obj, msg = nil); end 101 | 102 | sig { params(block: T.proc.void).returns(TrueClass) } 103 | def refute_pattern(&block); end 104 | 105 | sig { params(o1: T.anything, op: T.any(Symbol, String), o2: T.anything, msg: T.anything).returns(TrueClass) } 106 | def refute_operator(o1, op, o2 = T.unsafe(nil), msg = nil); end 107 | 108 | sig { params(path: T.any(String, Pathname), msg: T.anything).returns(TrueClass) } 109 | def refute_path_exists(path, msg = nil); end 110 | 111 | sig { params(o1: T.anything, op: T.any(String, Symbol), msg: T.anything).returns(TrueClass) } 112 | def refute_predicate(o1, op, msg = nil); end 113 | 114 | sig { params(obj: T.anything, meth: T.any(String, Symbol), msg: T.anything, include_all: T::Boolean).returns(TrueClass) } 115 | def refute_respond_to(obj, meth, msg = nil, include_all: false); end 116 | 117 | sig { params(exp: T.anything, act: T.anything, msg: T.anything).returns(TrueClass) } 118 | def refute_same(exp, act, msg = nil); end 119 | end 120 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/rbi/dsl/.gitattributes: -------------------------------------------------------------------------------- 1 | **/*.rbi linguist-generated=true 2 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/rbi/dsl/active_support/callbacks.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for dynamic methods in `ActiveSupport::Callbacks`. 5 | # Please instead update this file by running `bin/tapioca dsl ActiveSupport::Callbacks`. 6 | 7 | 8 | module ActiveSupport::Callbacks 9 | include GeneratedInstanceMethods 10 | 11 | mixes_in_class_methods GeneratedClassMethods 12 | 13 | module GeneratedClassMethods 14 | def __callbacks; end 15 | def __callbacks=(value); end 16 | end 17 | 18 | module GeneratedInstanceMethods 19 | def __callbacks; end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/rbi/gems/.gitattributes: -------------------------------------------------------------------------------- 1 | **/*.rbi linguist-generated=true 2 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/rbi/gems/bigdecimal@3.1.8.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `bigdecimal` gem. 5 | # Please instead update this file by running `bin/tapioca gem bigdecimal`. 6 | 7 | 8 | # source://bigdecimal//lib/bigdecimal/util.rb#78 9 | class BigDecimal < ::Numeric 10 | # call-seq: 11 | # a.to_d -> bigdecimal 12 | # 13 | # Returns self. 14 | # 15 | # require 'bigdecimal/util' 16 | # 17 | # d = BigDecimal("3.14") 18 | # d.to_d # => 0.314e1 19 | # 20 | # source://bigdecimal//lib/bigdecimal/util.rb#110 21 | def to_d; end 22 | 23 | # call-seq: 24 | # a.to_digits -> string 25 | # 26 | # Converts a BigDecimal to a String of the form "nnnnnn.mmm". 27 | # This method is deprecated; use BigDecimal#to_s("F") instead. 28 | # 29 | # require 'bigdecimal/util' 30 | # 31 | # d = BigDecimal("3.14") 32 | # d.to_digits # => "3.14" 33 | # 34 | # source://bigdecimal//lib/bigdecimal/util.rb#90 35 | def to_digits; end 36 | end 37 | 38 | BigDecimal::VERSION = T.let(T.unsafe(nil), String) 39 | 40 | # source://bigdecimal//lib/bigdecimal/util.rb#138 41 | class Complex < ::Numeric 42 | # call-seq: 43 | # cmp.to_d -> bigdecimal 44 | # cmp.to_d(precision) -> bigdecimal 45 | # 46 | # Returns the value as a BigDecimal. 47 | # 48 | # The +precision+ parameter is required for a rational complex number. 49 | # This parameter is used to determine the number of significant digits 50 | # for the result. 51 | # 52 | # require 'bigdecimal' 53 | # require 'bigdecimal/util' 54 | # 55 | # Complex(0.1234567, 0).to_d(4) # => 0.1235e0 56 | # Complex(Rational(22, 7), 0).to_d(3) # => 0.314e1 57 | # 58 | # See also Kernel.BigDecimal. 59 | # 60 | # source://bigdecimal//lib/bigdecimal/util.rb#157 61 | def to_d(*args); end 62 | end 63 | 64 | # source://bigdecimal//lib/bigdecimal/util.rb#171 65 | class NilClass 66 | # call-seq: 67 | # nil.to_d -> bigdecimal 68 | # 69 | # Returns nil represented as a BigDecimal. 70 | # 71 | # require 'bigdecimal' 72 | # require 'bigdecimal/util' 73 | # 74 | # nil.to_d # => 0.0 75 | # 76 | # source://bigdecimal//lib/bigdecimal/util.rb#182 77 | def to_d; end 78 | end 79 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/rbi/gems/connection_pool@2.4.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `connection_pool` gem. 5 | # Please instead update this file by running `bin/tapioca gem connection_pool`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/rbi/gems/erubi@1.13.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `erubi` gem. 5 | # Please instead update this file by running `bin/tapioca gem erubi`. 6 | 7 | 8 | # source://erubi//lib/erubi.rb#3 9 | module Erubi 10 | private 11 | 12 | def h(_arg0); end 13 | 14 | class << self 15 | def h(_arg0); end 16 | end 17 | end 18 | 19 | # source://erubi//lib/erubi.rb#51 20 | class Erubi::Engine 21 | # Initialize a new Erubi::Engine. Options: 22 | # +:bufval+ :: The value to use for the buffer variable, as a string (default '::String.new'). 23 | # +:bufvar+ :: The variable name to use for the buffer variable, as a string. 24 | # +:chain_appends+ :: Whether to chain << calls to the buffer variable. Offers better 25 | # performance, but can cause issues when the buffer variable is reassigned during 26 | # template rendering (default +false+). 27 | # +:ensure+ :: Wrap the template in a begin/ensure block restoring the previous value of bufvar. 28 | # +:escapefunc+ :: The function to use for escaping, as a string (default: '::Erubi.h'). 29 | # +:escape+ :: Whether to make <%= escape by default, and <%== not escape by default. 30 | # +:escape_html+ :: Same as +:escape+, with lower priority. 31 | # +:filename+ :: The filename for the template. 32 | # the resulting source code. Note this may cause problems if you are wrapping the resulting 33 | # source code in other code, because the magic comment only has an effect at the beginning of 34 | # the file, and having the magic comment later in the file can trigger warnings. 35 | # +:freeze_template_literals+ :: Whether to suffix all literal strings for template code with .freeze 36 | # (default: +true+ on Ruby 2.1+, +false+ on Ruby 2.0 and older). 37 | # Can be set to +false+ on Ruby 2.3+ when frozen string literals are enabled 38 | # in order to improve performance. 39 | # +:literal_prefix+ :: The prefix to output when using escaped tag delimiters (default '<%'). 40 | # +:literal_postfix+ :: The postfix to output when using escaped tag delimiters (default '%>'). 41 | # +:outvar+ :: Same as +:bufvar+, with lower priority. 42 | # +:postamble+ :: The postamble for the template, by default returns the resulting source code. 43 | # +:preamble+ :: The preamble for the template, by default initializes the buffer variable. 44 | # +:regexp+ :: The regexp to use for scanning. 45 | # +:src+ :: The initial value to use for the source code, an empty string by default. 46 | # +:trim+ :: Whether to trim leading and trailing whitespace, true by default. 47 | # 48 | # @return [Engine] a new instance of Engine 49 | # 50 | # source://erubi//lib/erubi.rb#91 51 | def initialize(input, properties = T.unsafe(nil)); end 52 | 53 | # The variable name used for the buffer variable. 54 | # 55 | # source://erubi//lib/erubi.rb#62 56 | def bufvar; end 57 | 58 | # The filename of the template, if one was given. 59 | # 60 | # source://erubi//lib/erubi.rb#59 61 | def filename; end 62 | 63 | # The frozen ruby source code generated from the template, which can be evaled. 64 | # 65 | # source://erubi//lib/erubi.rb#56 66 | def src; end 67 | 68 | private 69 | 70 | # Add ruby code to the template 71 | # 72 | # source://erubi//lib/erubi.rb#223 73 | def add_code(code); end 74 | 75 | # Add the given ruby expression result to the template, 76 | # escaping it based on the indicator given and escape flag. 77 | # 78 | # source://erubi//lib/erubi.rb#232 79 | def add_expression(indicator, code); end 80 | 81 | # Add the result of Ruby expression to the template 82 | # 83 | # source://erubi//lib/erubi.rb#241 84 | def add_expression_result(code); end 85 | 86 | # Add the escaped result of Ruby expression to the template 87 | # 88 | # source://erubi//lib/erubi.rb#246 89 | def add_expression_result_escaped(code); end 90 | 91 | # Add the given postamble to the src. Can be overridden in subclasses 92 | # to make additional changes to src that depend on the current state. 93 | # 94 | # source://erubi//lib/erubi.rb#252 95 | def add_postamble(postamble); end 96 | 97 | # Add raw text to the template. Modifies argument if argument is mutable as a memory optimization. 98 | # Must be called with a string, cannot be called with nil (Rails's subclass depends on it). 99 | # 100 | # source://erubi//lib/erubi.rb#210 101 | def add_text(text); end 102 | 103 | # Raise an exception, as the base engine class does not support handling other indicators. 104 | # 105 | # @raise [ArgumentError] 106 | # 107 | # source://erubi//lib/erubi.rb#258 108 | def handle(indicator, code, tailch, rspace, lspace); end 109 | 110 | # Make sure that any current expression has been terminated. 111 | # The default is to terminate all expressions, but when 112 | # the chain_appends option is used, expressions may not be 113 | # terminated. 114 | # 115 | # source://erubi//lib/erubi.rb#286 116 | def terminate_expression; end 117 | 118 | # Make sure the buffer variable is the target of the next append 119 | # before yielding to the block. Mark that the buffer is the target 120 | # of the next append after the block executes. 121 | # 122 | # This method should only be called if the block will result in 123 | # code where << will append to the bufvar. 124 | # 125 | # source://erubi//lib/erubi.rb#268 126 | def with_buffer; end 127 | end 128 | 129 | # The default regular expression used for scanning. 130 | # 131 | # source://erubi//lib/erubi.rb#53 132 | Erubi::Engine::DEFAULT_REGEXP = T.let(T.unsafe(nil), Regexp) 133 | 134 | # source://erubi//lib/erubi.rb#17 135 | Erubi::FREEZE_TEMPLATE_LITERALS = T.let(T.unsafe(nil), TrueClass) 136 | 137 | # source://erubi//lib/erubi.rb#15 138 | Erubi::MATCH_METHOD = T.let(T.unsafe(nil), Symbol) 139 | 140 | # source://erubi//lib/erubi.rb#8 141 | Erubi::RANGE_FIRST = T.let(T.unsafe(nil), Integer) 142 | 143 | # source://erubi//lib/erubi.rb#9 144 | Erubi::RANGE_LAST = T.let(T.unsafe(nil), Integer) 145 | 146 | # source://erubi//lib/erubi.rb#16 147 | Erubi::SKIP_DEFINED_FOR_INSTANCE_VARIABLE = T.let(T.unsafe(nil), TrueClass) 148 | 149 | # source://erubi//lib/erubi.rb#4 150 | Erubi::VERSION = T.let(T.unsafe(nil), String) 151 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/rbi/gems/multi_json@1.15.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `multi_json` gem. 5 | # Please instead update this file by running `bin/tapioca gem multi_json`. 6 | 7 | 8 | # source://multi_json//lib/multi_json/options.rb#1 9 | module MultiJson 10 | include ::MultiJson::Options 11 | extend ::MultiJson::Options 12 | extend ::MultiJson 13 | 14 | # Get the current adapter class. 15 | # 16 | # source://multi_json//lib/multi_json.rb#70 17 | def adapter; end 18 | 19 | # Set the JSON parser utilizing a symbol, string, or class. 20 | # Supported by default are: 21 | # 22 | # * :oj 23 | # * :json_gem 24 | # * :json_pure 25 | # * :ok_json 26 | # * :yajl 27 | # * :nsjsonserialization (MacRuby only) 28 | # * :gson (JRuby only) 29 | # * :jr_jackson (JRuby only) 30 | # 31 | # source://multi_json//lib/multi_json.rb#90 32 | def adapter=(new_adapter); end 33 | 34 | # source://multi_json//lib/multi_json.rb#26 35 | def cached_options(*_arg0); end 36 | 37 | # source://multi_json//lib/multi_json.rb#129 38 | def current_adapter(options = T.unsafe(nil)); end 39 | 40 | # Decode a JSON string into Ruby. 41 | # 42 | # Options 43 | # 44 | # :symbolize_keys :: If true, will use symbols instead of strings for the keys. 45 | # :adapter :: If set, the selected adapter will be used for this call. 46 | # 47 | # source://multi_json//lib/multi_json.rb#119 48 | def decode(string, options = T.unsafe(nil)); end 49 | 50 | # The default adapter based on what you currently 51 | # have loaded and installed. First checks to see 52 | # if any adapters are already loaded, then checks 53 | # to see which are installed if none are loaded. 54 | # 55 | # source://multi_json//lib/multi_json.rb#46 56 | def default_adapter; end 57 | 58 | # The default adapter based on what you currently 59 | # have loaded and installed. First checks to see 60 | # if any adapters are already loaded, then checks 61 | # to see which are installed if none are loaded. 62 | # 63 | # source://multi_json//lib/multi_json.rb#46 64 | def default_engine; end 65 | 66 | # source://multi_json//lib/multi_json.rb#18 67 | def default_options; end 68 | 69 | # source://multi_json//lib/multi_json.rb#11 70 | def default_options=(value); end 71 | 72 | # Encodes a Ruby object as JSON. 73 | # 74 | # source://multi_json//lib/multi_json.rb#138 75 | def dump(object, options = T.unsafe(nil)); end 76 | 77 | # Encodes a Ruby object as JSON. 78 | # 79 | # source://multi_json//lib/multi_json.rb#138 80 | def encode(object, options = T.unsafe(nil)); end 81 | 82 | # Get the current adapter class. 83 | # 84 | # source://multi_json//lib/multi_json.rb#70 85 | def engine; end 86 | 87 | # Set the JSON parser utilizing a symbol, string, or class. 88 | # Supported by default are: 89 | # 90 | # * :oj 91 | # * :json_gem 92 | # * :json_pure 93 | # * :ok_json 94 | # * :yajl 95 | # * :nsjsonserialization (MacRuby only) 96 | # * :gson (JRuby only) 97 | # * :jr_jackson (JRuby only) 98 | # 99 | # source://multi_json//lib/multi_json.rb#90 100 | def engine=(new_adapter); end 101 | 102 | # Decode a JSON string into Ruby. 103 | # 104 | # Options 105 | # 106 | # :symbolize_keys :: If true, will use symbols instead of strings for the keys. 107 | # :adapter :: If set, the selected adapter will be used for this call. 108 | # 109 | # source://multi_json//lib/multi_json.rb#119 110 | def load(string, options = T.unsafe(nil)); end 111 | 112 | # source://multi_json//lib/multi_json.rb#98 113 | def load_adapter(new_adapter); end 114 | 115 | # source://multi_json//lib/multi_json.rb#26 116 | def reset_cached_options!(*_arg0); end 117 | 118 | # Set the JSON parser utilizing a symbol, string, or class. 119 | # Supported by default are: 120 | # 121 | # * :oj 122 | # * :json_gem 123 | # * :json_pure 124 | # * :ok_json 125 | # * :yajl 126 | # * :nsjsonserialization (MacRuby only) 127 | # * :gson (JRuby only) 128 | # * :jr_jackson (JRuby only) 129 | # 130 | # source://multi_json//lib/multi_json.rb#90 131 | def use(new_adapter); end 132 | 133 | # Executes passed block using specified adapter. 134 | # 135 | # source://multi_json//lib/multi_json.rb#144 136 | def with_adapter(new_adapter); end 137 | 138 | # Executes passed block using specified adapter. 139 | # 140 | # source://multi_json//lib/multi_json.rb#144 141 | def with_engine(new_adapter); end 142 | 143 | private 144 | 145 | # source://multi_json//lib/multi_json.rb#155 146 | def load_adapter_from_string_name(name); end 147 | end 148 | 149 | # source://multi_json//lib/multi_json.rb#31 150 | MultiJson::ALIASES = T.let(T.unsafe(nil), Hash) 151 | 152 | # source://multi_json//lib/multi_json/adapter_error.rb#2 153 | class MultiJson::AdapterError < ::ArgumentError 154 | # Returns the value of attribute cause. 155 | # 156 | # source://multi_json//lib/multi_json/adapter_error.rb#3 157 | def cause; end 158 | 159 | class << self 160 | # source://multi_json//lib/multi_json/adapter_error.rb#5 161 | def build(original_exception); end 162 | end 163 | end 164 | 165 | # Legacy support 166 | # 167 | # source://multi_json//lib/multi_json/parse_error.rb#16 168 | MultiJson::DecodeError = MultiJson::ParseError 169 | 170 | # source://multi_json//lib/multi_json/parse_error.rb#16 171 | MultiJson::LoadError = MultiJson::ParseError 172 | 173 | # source://multi_json//lib/multi_json/options.rb#2 174 | module MultiJson::Options 175 | # source://multi_json//lib/multi_json/options.rb#25 176 | def default_dump_options; end 177 | 178 | # source://multi_json//lib/multi_json/options.rb#21 179 | def default_load_options; end 180 | 181 | # source://multi_json//lib/multi_json/options.rb#17 182 | def dump_options(*args); end 183 | 184 | # source://multi_json//lib/multi_json/options.rb#8 185 | def dump_options=(options); end 186 | 187 | # source://multi_json//lib/multi_json/options.rb#13 188 | def load_options(*args); end 189 | 190 | # source://multi_json//lib/multi_json/options.rb#3 191 | def load_options=(options); end 192 | 193 | private 194 | 195 | # source://multi_json//lib/multi_json/options.rb#31 196 | def get_options(options, *args); end 197 | end 198 | 199 | # source://multi_json//lib/multi_json/options_cache.rb#2 200 | module MultiJson::OptionsCache 201 | extend ::MultiJson::OptionsCache 202 | 203 | # source://multi_json//lib/multi_json/options_cache.rb#10 204 | def fetch(type, key, &block); end 205 | 206 | # source://multi_json//lib/multi_json/options_cache.rb#5 207 | def reset; end 208 | 209 | private 210 | 211 | # source://multi_json//lib/multi_json/options_cache.rb#24 212 | def write(cache, key); end 213 | end 214 | 215 | # Normally MultiJson is used with a few option sets for both dump/load 216 | # methods. When options are generated dynamically though, every call would 217 | # cause a cache miss and the cache would grow indefinitely. To prevent 218 | # this, we just reset the cache every time the number of keys outgrows 219 | # 1000. 220 | # 221 | # source://multi_json//lib/multi_json/options_cache.rb#22 222 | MultiJson::OptionsCache::MAX_CACHE_SIZE = T.let(T.unsafe(nil), Integer) 223 | 224 | # source://multi_json//lib/multi_json/parse_error.rb#2 225 | class MultiJson::ParseError < ::StandardError 226 | # Returns the value of attribute cause. 227 | # 228 | # source://multi_json//lib/multi_json/parse_error.rb#3 229 | def cause; end 230 | 231 | # Returns the value of attribute data. 232 | # 233 | # source://multi_json//lib/multi_json/parse_error.rb#3 234 | def data; end 235 | 236 | class << self 237 | # source://multi_json//lib/multi_json/parse_error.rb#5 238 | def build(original_exception, data); end 239 | end 240 | end 241 | 242 | # source://multi_json//lib/multi_json.rb#33 243 | MultiJson::REQUIREMENT_MAP = T.let(T.unsafe(nil), Array) 244 | 245 | # source://multi_json//lib/multi_json/version.rb#16 246 | MultiJson::VERSION = T.let(T.unsafe(nil), String) 247 | 248 | # source://multi_json//lib/multi_json/version.rb#2 249 | class MultiJson::Version 250 | class << self 251 | # @return [String] 252 | # 253 | # source://multi_json//lib/multi_json/version.rb#10 254 | def to_s; end 255 | end 256 | end 257 | 258 | # source://multi_json//lib/multi_json/version.rb#3 259 | MultiJson::Version::MAJOR = T.let(T.unsafe(nil), Integer) 260 | 261 | # source://multi_json//lib/multi_json/version.rb#4 262 | MultiJson::Version::MINOR = T.let(T.unsafe(nil), Integer) 263 | 264 | # source://multi_json//lib/multi_json/version.rb#5 265 | MultiJson::Version::PATCH = T.let(T.unsafe(nil), Integer) 266 | 267 | # source://multi_json//lib/multi_json/version.rb#6 268 | MultiJson::Version::PRE = T.let(T.unsafe(nil), T.untyped) 269 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/rbi/gems/netrc@0.11.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `netrc` gem. 5 | # Please instead update this file by running `bin/tapioca gem netrc`. 6 | 7 | 8 | # source://netrc//lib/netrc.rb#3 9 | class Netrc 10 | # @return [Netrc] a new instance of Netrc 11 | # 12 | # source://netrc//lib/netrc.rb#166 13 | def initialize(path, data); end 14 | 15 | # source://netrc//lib/netrc.rb#180 16 | def [](k); end 17 | 18 | # source://netrc//lib/netrc.rb#188 19 | def []=(k, info); end 20 | 21 | # source://netrc//lib/netrc.rb#200 22 | def delete(key); end 23 | 24 | # source://netrc//lib/netrc.rb#211 25 | def each(&block); end 26 | 27 | # source://netrc//lib/netrc.rb#196 28 | def length; end 29 | 30 | # source://netrc//lib/netrc.rb#215 31 | def new_item(m, l, p); end 32 | 33 | # Returns the value of attribute new_item_prefix. 34 | # 35 | # source://netrc//lib/netrc.rb#178 36 | def new_item_prefix; end 37 | 38 | # Sets the attribute new_item_prefix 39 | # 40 | # @param value the value to set the attribute new_item_prefix to. 41 | # 42 | # source://netrc//lib/netrc.rb#178 43 | def new_item_prefix=(_arg0); end 44 | 45 | # source://netrc//lib/netrc.rb#219 46 | def save; end 47 | 48 | # source://netrc//lib/netrc.rb#233 49 | def unparse; end 50 | 51 | class << self 52 | # source://netrc//lib/netrc.rb#42 53 | def check_permissions(path); end 54 | 55 | # source://netrc//lib/netrc.rb#33 56 | def config; end 57 | 58 | # @yield [self.config] 59 | # 60 | # source://netrc//lib/netrc.rb#37 61 | def configure; end 62 | 63 | # source://netrc//lib/netrc.rb#10 64 | def default_path; end 65 | 66 | # source://netrc//lib/netrc.rb#14 67 | def home_path; end 68 | 69 | # source://netrc//lib/netrc.rb#85 70 | def lex(lines); end 71 | 72 | # source://netrc//lib/netrc.rb#29 73 | def netrc_filename; end 74 | 75 | # Returns two values, a header and a list of items. 76 | # Each item is a tuple, containing some or all of: 77 | # - machine keyword (including trailing whitespace+comments) 78 | # - machine name 79 | # - login keyword (including surrounding whitespace+comments) 80 | # - login 81 | # - password keyword (including surrounding whitespace+comments) 82 | # - password 83 | # - trailing chars 84 | # This lets us change individual fields, then write out the file 85 | # with all its original formatting. 86 | # 87 | # source://netrc//lib/netrc.rb#129 88 | def parse(ts); end 89 | 90 | # Reads path and parses it as a .netrc file. If path doesn't 91 | # exist, returns an empty object. Decrypt paths ending in .gpg. 92 | # 93 | # source://netrc//lib/netrc.rb#51 94 | def read(path = T.unsafe(nil)); end 95 | 96 | # @return [Boolean] 97 | # 98 | # source://netrc//lib/netrc.rb#112 99 | def skip?(s); end 100 | end 101 | end 102 | 103 | # source://netrc//lib/netrc.rb#8 104 | Netrc::CYGWIN = T.let(T.unsafe(nil), T.untyped) 105 | 106 | # source://netrc//lib/netrc.rb#244 107 | class Netrc::Entry < ::Struct 108 | # Returns the value of attribute login 109 | # 110 | # @return [Object] the current value of login 111 | def login; end 112 | 113 | # Sets the attribute login 114 | # 115 | # @param value [Object] the value to set the attribute login to. 116 | # @return [Object] the newly set value 117 | def login=(_); end 118 | 119 | # Returns the value of attribute password 120 | # 121 | # @return [Object] the current value of password 122 | def password; end 123 | 124 | # Sets the attribute password 125 | # 126 | # @param value [Object] the value to set the attribute password to. 127 | # @return [Object] the newly set value 128 | def password=(_); end 129 | 130 | def to_ary; end 131 | 132 | class << self 133 | def [](*_arg0); end 134 | def inspect; end 135 | def keyword_init?; end 136 | def members; end 137 | def new(*_arg0); end 138 | end 139 | end 140 | 141 | # source://netrc//lib/netrc.rb#250 142 | class Netrc::Error < ::StandardError; end 143 | 144 | # source://netrc//lib/netrc.rb#68 145 | class Netrc::TokenArray < ::Array 146 | # source://netrc//lib/netrc.rb#76 147 | def readto; end 148 | 149 | # source://netrc//lib/netrc.rb#69 150 | def take; end 151 | end 152 | 153 | # source://netrc//lib/netrc.rb#4 154 | Netrc::VERSION = T.let(T.unsafe(nil), String) 155 | 156 | # see http://stackoverflow.com/questions/4871309/what-is-the-correct-way-to-detect-if-ruby-is-running-on-windows 157 | # 158 | # source://netrc//lib/netrc.rb#7 159 | Netrc::WINDOWS = T.let(T.unsafe(nil), T.untyped) 160 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/rbi/gems/polyfill@1.9.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `polyfill` gem. 5 | # Please instead update this file by running `bin/tapioca gem polyfill`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/rbi/gems/rspec@3.13.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `rspec` gem. 5 | # Please instead update this file by running `bin/tapioca gem rspec`. 6 | 7 | 8 | # source://rspec//lib/rspec/version.rb#1 9 | module RSpec 10 | class << self 11 | # source://rspec-core/3.13.1/lib/rspec/core.rb#70 12 | def clear_examples; end 13 | 14 | # source://rspec-core/3.13.1/lib/rspec/core.rb#85 15 | def configuration; end 16 | 17 | # source://rspec-core/3.13.1/lib/rspec/core.rb#49 18 | def configuration=(_arg0); end 19 | 20 | # source://rspec-core/3.13.1/lib/rspec/core.rb#97 21 | def configure; end 22 | 23 | # source://rspec-core/3.13.1/lib/rspec/core.rb#194 24 | def const_missing(name); end 25 | 26 | # source://rspec-core/3.13.1/lib/rspec/core/dsl.rb#42 27 | def context(*args, &example_group_block); end 28 | 29 | # source://rspec-core/3.13.1/lib/rspec/core.rb#122 30 | def current_example; end 31 | 32 | # source://rspec-core/3.13.1/lib/rspec/core.rb#128 33 | def current_example=(example); end 34 | 35 | # source://rspec-core/3.13.1/lib/rspec/core.rb#154 36 | def current_scope; end 37 | 38 | # source://rspec-core/3.13.1/lib/rspec/core.rb#134 39 | def current_scope=(scope); end 40 | 41 | # source://rspec-core/3.13.1/lib/rspec/core/dsl.rb#42 42 | def describe(*args, &example_group_block); end 43 | 44 | # source://rspec-core/3.13.1/lib/rspec/core/dsl.rb#42 45 | def example_group(*args, &example_group_block); end 46 | 47 | # source://rspec-core/3.13.1/lib/rspec/core/dsl.rb#42 48 | def fcontext(*args, &example_group_block); end 49 | 50 | # source://rspec-core/3.13.1/lib/rspec/core/dsl.rb#42 51 | def fdescribe(*args, &example_group_block); end 52 | 53 | # source://rspec-core/3.13.1/lib/rspec/core.rb#58 54 | def reset; end 55 | 56 | # source://rspec-core/3.13.1/lib/rspec/core/shared_example_group.rb#110 57 | def shared_context(name, *args, &block); end 58 | 59 | # source://rspec-core/3.13.1/lib/rspec/core/shared_example_group.rb#110 60 | def shared_examples(name, *args, &block); end 61 | 62 | # source://rspec-core/3.13.1/lib/rspec/core/shared_example_group.rb#110 63 | def shared_examples_for(name, *args, &block); end 64 | 65 | # source://rspec-core/3.13.1/lib/rspec/core.rb#160 66 | def world; end 67 | 68 | # source://rspec-core/3.13.1/lib/rspec/core.rb#49 69 | def world=(_arg0); end 70 | 71 | # source://rspec-core/3.13.1/lib/rspec/core/dsl.rb#42 72 | def xcontext(*args, &example_group_block); end 73 | 74 | # source://rspec-core/3.13.1/lib/rspec/core/dsl.rb#42 75 | def xdescribe(*args, &example_group_block); end 76 | end 77 | end 78 | 79 | # source://rspec//lib/rspec/version.rb#2 80 | module RSpec::Version; end 81 | 82 | # source://rspec//lib/rspec/version.rb#3 83 | RSpec::Version::STRING = T.let(T.unsafe(nil), String) 84 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/rbi/gems/ruby2_keywords@0.0.5.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `ruby2_keywords` gem. 5 | # Please instead update this file by running `bin/tapioca gem ruby2_keywords`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/rbi/gems/safe_type@1.1.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `safe_type` gem. 5 | # Please instead update this file by running `bin/tapioca gem safe_type`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/rbi/gems/sorbet-coerce@0.7.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `sorbet-coerce` gem. 5 | # Please instead update this file by running `bin/tapioca gem sorbet-coerce`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/tapioca/config.yml: -------------------------------------------------------------------------------- 1 | gem: 2 | # Add your `gem` command parameters here: 3 | # 4 | # exclude: 5 | # - gem_name 6 | # doc: true 7 | # workers: 5 8 | dsl: 9 | # Add your `dsl` command parameters here: 10 | # 11 | # exclude: 12 | # - SomeGeneratorName 13 | # workers: 5 14 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/sorbet/tapioca/require.rb: -------------------------------------------------------------------------------- 1 | # typed: true 2 | # frozen_string_literal: true 3 | 4 | require "pry" 5 | require "rack/test" 6 | require "resource_registry" 7 | require "rspec" 8 | require "sinatra" 9 | require "sinatra/multi_route" 10 | require "sinatra/reloader" 11 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/spec/app_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | describe App do 6 | let(:app) { App.new } 7 | 8 | context "GET /dogs" do 9 | let(:response) { get "/dogs" } 10 | 11 | it "returns 200 OK" do 12 | expect(response.status).to eq 200 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /examples/sinatra-pet-shelter/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV["APP_ENV"] = "test" 4 | 5 | require_relative "../lib/app" 6 | require "rspec" 7 | require "rack/test" 8 | 9 | RSpec.configure { |_config| include Rack::Test::Methods } 10 | -------------------------------------------------------------------------------- /lib/README.md: -------------------------------------------------------------------------------- 1 | # Resource Registry 2 | 3 | A service discovery mechanism for Factorial backend 4 | 5 | ## Features 6 | 7 | - Schema registry for resources, maybe we can infere them from entities 8 | - Relate events to resources actions (CRUD and not CRUD) 9 | 10 | ## Examples 11 | 12 | ```ruby 13 | ResourceRegistry.add( 14 | Resource.new( 15 | slug: 'employees', # Maybe we will need component Namespacing / Category to prefix routes 16 | repository: Employees::Repositories::Employees, 17 | verbs: { 18 | index: { 19 | type: :read 20 | }, # => Repository.read 21 | show: { 22 | type: :read 23 | }, 24 | create: { 25 | type: :write 26 | }, # => Repository.create 27 | update: { 28 | type: :write 29 | }, 30 | destroy: { 31 | event: Employee::Events::EmployeeRemoved 32 | }, 33 | # RPCs they are all http POST method 34 | approve: { 35 | event: Employee::Events::EmployeeApproved 36 | } # => Repository.approve 37 | } 38 | ) 39 | ) 40 | ``` 41 | -------------------------------------------------------------------------------- /lib/package.yml: -------------------------------------------------------------------------------- 1 | --- 2 | enforce_privacy: true 3 | enforce_dependencies: true 4 | dependencies: 5 | - '.' 6 | - 'components/telemetry' 7 | metadata: 8 | owner: '@factorialco/developer-experience' 9 | -------------------------------------------------------------------------------- /lib/package_todo.yml: -------------------------------------------------------------------------------- 1 | # This file contains a list of dependencies that are not part of the long term plan for the 2 | # 'lib/resource_registry' package. 3 | # We should generally work to reduce this list over time. 4 | # 5 | # You can regenerate this file using the following command: 6 | # 7 | # bin/packwerk update-todo 8 | --- 9 | components/api_public: 10 | "::ApiPublic::Authentication::InvalidState": 11 | violations: 12 | - dependency 13 | - privacy 14 | files: 15 | - lib/resource_registry/graphql/base_schema.rb 16 | components/features: 17 | "::Features::Catalog::DEV_REALTIME_ENGINE": 18 | violations: 19 | - dependency 20 | files: 21 | - lib/resource_registry/public/trigger.rb 22 | components/permissions: 23 | "::Permissions::PolicyContext": 24 | violations: 25 | - dependency 26 | files: 27 | - lib/resource_registry/graphql/graphql_repository_source.rb 28 | - lib/resource_registry/public/graphql/resource_query.rb 29 | -------------------------------------------------------------------------------- /lib/resource_registry.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: true 3 | 4 | require "sorbet-runtime" 5 | require "active_support/all" 6 | 7 | require "resource_registry/configuration" 8 | require "resource_registry/resource" 9 | require "resource_registry/versions" 10 | require "resource_registry/versions/version" 11 | require "resource_registry/entity_finder" 12 | require "resource_registry/registry" 13 | require "resource_registry/serializer" 14 | require "resource_registry/repositories/base" 15 | require "schema_registry/registry" 16 | require "schema_registry/json_schema_mapper" 17 | require "schema_registry/maybe" 18 | require "runtime_generic" 19 | 20 | # Entry point for ResourceRegistry 21 | module ResourceRegistry 22 | class << self 23 | extend T::Sig 24 | 25 | sig { returns(Configuration) } 26 | def configuration 27 | @configuration ||= Configuration.new 28 | end 29 | 30 | sig { void } 31 | def configure 32 | yield(configuration) 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/resource_registry/capabilities/capability_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: strict 3 | 4 | module ResourceRegistry 5 | module Capabilities 6 | # Represents configuration for a specific resource capability 7 | module CapabilityConfig 8 | extend T::Helpers 9 | extend T::Sig 10 | include Kernel 11 | 12 | interface! 13 | 14 | # Class methods interface for capability configuration 15 | module ClassMethods 16 | extend T::Sig 17 | extend T::Generic 18 | extend T::Helpers 19 | abstract! 20 | 21 | has_attached_class! 22 | 23 | # The key of the capability, this key will be used to take it from yaml configuration 24 | sig { abstract.returns(Symbol) } 25 | def key 26 | end 27 | 28 | sig { params(resource: Resource).returns(T::Boolean) } 29 | def resource_capability?(resource:) 30 | resource.capabilities.key?(key) 31 | end 32 | 33 | sig do 34 | params(resource: Resource).returns( 35 | T.nilable(T.attached_class) 36 | ) 37 | end 38 | def resource_capability(resource:) 39 | return unless resource_capability?(resource:) 40 | 41 | T.cast(resource.capabilities[key], T.attached_class) 42 | end 43 | 44 | sig do 45 | params(resource: Resource).returns(T.attached_class) 46 | end 47 | def resource_capability!(resource:) 48 | T.must(resource_capability(resource: )) 49 | end 50 | end 51 | 52 | requires_ancestor { Object } 53 | 54 | mixes_in_class_methods(ClassMethods) 55 | 56 | sig { abstract.returns(T::Hash[String, T.untyped]) } 57 | def serialize 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/resource_registry/capability_factory.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module ResourceRegistry 4 | # This class enumerates features that are available to a resource. Each 5 | # resource knows which subset of these it needs to be used with. 6 | class CapabilityFactory 7 | extend T::Sig 8 | 9 | sig do 10 | params( 11 | data: T::Hash[String, T.untyped], 12 | capabilities: 13 | T::Hash[ 14 | Symbol, 15 | T.all( 16 | T::Class[Capabilities::CapabilityConfig], 17 | T.class_of(T::Struct) 18 | ) 19 | ] 20 | ).returns(Capabilities::CapabilityConfig) 21 | end 22 | def self.load( 23 | data, 24 | capabilities: ResourceRegistry.configuration.capabilities 25 | ) 26 | key = data["key"] 27 | capability = capabilities.fetch(key.to_sym) 28 | # FIXME: This T.let should not be needed 29 | T.let(capability, T.class_of(T::Struct)).from_hash(data) 30 | end 31 | 32 | sig do 33 | params(capability: Capabilities::CapabilityConfig).returns( 34 | T::Hash[String, T.untyped] 35 | ) 36 | end 37 | def self.dump(capability) 38 | { "key" => capability.class.key }.merge!(capability.serialize) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/resource_registry/configuration.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: true 3 | 4 | require_relative "relationship_types/has_many" 5 | require_relative "relationship_types/has_many_through" 6 | require_relative "relationship_types/has_one" 7 | require_relative "relationship_types/belongs_to" 8 | 9 | module ResourceRegistry 10 | class Configuration 11 | extend T::Sig 12 | 13 | DEFAULT_RELATIONSHIP_TYPES = { 14 | "has_many_through" => RelationshipTypes::HasManyThrough, 15 | "has_many" => RelationshipTypes::HasMany, 16 | "has_one" => RelationshipTypes::HasOne, 17 | "belongs_to" => RelationshipTypes::BelongsTo 18 | }.freeze 19 | 20 | sig { void } 21 | def initialize 22 | @relationship_types = DEFAULT_RELATIONSHIP_TYPES.dup 23 | @capabilities = {} 24 | end 25 | 26 | sig { params(type: String, klass: T.class_of(RelationshipType)).void } 27 | def register_relationship_type(type, klass) 28 | @relationship_types[type] = klass 29 | end 30 | 31 | sig do 32 | params( 33 | capability: Symbol, 34 | klass: T.class_of(Capabilities::CapabilityConfig) 35 | ).void 36 | end 37 | def register_capability(capability, klass) 38 | @capabilities[capability] = klass 39 | end 40 | 41 | sig { returns(T::Hash[String, T::Class[RelationshipType]]) } 42 | attr_reader :relationship_types 43 | 44 | sig do 45 | returns( 46 | T::Hash[ 47 | Symbol, 48 | T.all(T::Class[Capabilities::CapabilityConfig], T.class_of(T::Struct)) 49 | ] 50 | ) 51 | end 52 | attr_reader :capabilities 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/resource_registry/entity_finder.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module ResourceRegistry 4 | class EntityFinder 5 | extend T::Sig 6 | 7 | sig do 8 | params(repository: T.class_of(Repositories::Base)).returns( 9 | T.nilable(T.class_of(T::Struct)) 10 | ) 11 | end 12 | def self.call(repository:) 13 | entity = repository.entity 14 | 15 | return nil if entity.is_a?(T::Types::Untyped) 16 | 17 | entity 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/resource_registry/load_resources_from_cache.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module ResourceRegistry 4 | class LoadResourcesFromCache 5 | extend T::Sig 6 | 7 | sig { returns(T::Array[Resource]) } 8 | def call 9 | # We early return to being able to generate this file in development, 10 | # otherwise the script generating it fails 11 | unless File.exist?(Rails.root.join("resources.json")) 12 | puts "Cache not found. Skipping loading resources from cache. Consider running `bin/rails resource_registry:generate_cache`" 13 | 14 | return [] 15 | end 16 | 17 | cache_path = Rails.root.join("resources.json") 18 | resources = JSON.load_file(cache_path) 19 | resources 20 | .map { |res_def| ResourceRegistry::Resource.load(res_def) } 21 | .sort_by(&:path) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/resource_registry/registry.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | # frozen_string_literal: true 3 | 4 | require_relative "resource" 5 | require_relative "capabilities/capability_config" 6 | 7 | module ResourceRegistry 8 | class Registry 9 | extend T::Sig 10 | 11 | class UnableToFindResourceError < StandardError 12 | end 13 | 14 | class DuplicatedIdentifierError < StandardError 15 | end 16 | 17 | sig { params(resources: T::Array[Resource]).void } 18 | def initialize(resources:) 19 | if duplicated_identifier?(resources) 20 | raise DuplicatedIdentifierError, 21 | "You have a duplicated resource in a component. Check that you don't have more than one repository with the same name in a component." 22 | end 23 | 24 | @resources = 25 | T.let( 26 | resources.index_by { |res| res.identifier.to_s }, 27 | T::Hash[String, Resource] 28 | ) 29 | end 30 | 31 | sig { params(identifier: String).returns(T.nilable(Resource)) } 32 | def fetch(identifier) 33 | resources[identifier] 34 | end 35 | 36 | sig { params(identifier: String).returns(Resource) } 37 | def fetch!(identifier) 38 | resource = fetch(identifier) 39 | 40 | return resource if resource.present? 41 | 42 | raise UnableToFindResourceError, "#{identifier} does not exist" 43 | end 44 | 45 | sig do 46 | params( 47 | repository_class: 48 | T::Class[ResourceRegistry::Repositories::Base[T.untyped]] 49 | ).returns(T.nilable(Resource)) 50 | end 51 | def fetch_for_repository(repository_class) 52 | resources_by_raw_repository[repository_class.to_s] 53 | end 54 | 55 | alias find_for_repository fetch_for_repository 56 | 57 | sig { returns(T::Hash[String, Resource]) } 58 | def fetch_all 59 | resources 60 | end 61 | 62 | sig do 63 | params(capabilities: T::Class[Capabilities::CapabilityConfig]).returns( 64 | T::Array[Resource] 65 | ) 66 | end 67 | def fetch_with_capabilities(*capabilities) 68 | # FIXME: This is a hack to avoid having to change the interface of the method 69 | capabilities_set = T.unsafe(capabilities).to_set(&:key) 70 | 71 | fetch_all.values.select do |resource| 72 | capabilities_set <= resource.capabilities.keys.to_set 73 | end 74 | end 75 | 76 | private 77 | 78 | sig { returns(T::Hash[String, Resource]) } 79 | attr_accessor :resources 80 | 81 | sig { returns(T::Hash[String, Resource]) } 82 | def resources_by_raw_repository 83 | @resources_by_raw_repository ||= 84 | T.let( 85 | resources.values.index_by(&:repository_raw), 86 | T.nilable(T::Hash[String, Resource]) 87 | ) 88 | end 89 | 90 | sig { params(resources: T::Array[Resource]).returns(T::Boolean) } 91 | def duplicated_identifier?(resources) 92 | resources.map(&:identifier).uniq.size != resources.size 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /lib/resource_registry/relationship.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: true 3 | 4 | require_relative "relationship_type" 5 | require_relative "relationship_type_factory" 6 | 7 | module ResourceRegistry 8 | class Relationship < T::Struct 9 | extend T::Sig 10 | 11 | const :name, String 12 | const :resource_id, Symbol 13 | const :field, Symbol 14 | const :primary_key, Symbol, default: :id 15 | const :type, RelationshipType 16 | const :fixed_dto_params, T.nilable(T::Hash[String, T.untyped]) 17 | const :optional, T::Boolean 18 | 19 | # Are there multiple resources in the other side of the relationship? 20 | sig { returns(T::Boolean) } 21 | def many_cardinality? 22 | type.many_cardinality? 23 | end 24 | 25 | sig { returns(T::Hash[String, T.untyped]) } 26 | def dump 27 | {}.tap do |result| 28 | result["name"] = name 29 | result["resource_id"] = resource_id 30 | result["primary_key"] = primary_key 31 | result["field"] = field 32 | result["type"] = type.serialize 33 | result["optional"] = optional 34 | result["fixed_dto_params"] = fixed_dto_params 35 | end 36 | end 37 | 38 | sig { params(spec: T::Hash[String, T.untyped]).returns(Relationship) } 39 | def self.load(spec) 40 | type = RelationshipTypeFactory.from_spec(spec) 41 | 42 | new( 43 | name: type.name, 44 | resource_id: type.resource_id, 45 | field: type.field, 46 | primary_key: type.primary_key, 47 | type: type, 48 | fixed_dto_params: spec["fixed_dto_params"], 49 | optional: !spec["optional"].to_s.casecmp("false").zero? 50 | ) 51 | end 52 | 53 | # We provide this in the dataloader, we encourage not to perform joins in 54 | # frontend, so we skip ids to be exposed. 55 | # FIXME: Review if this belongs to this layer or is coupled to GraphQL 56 | sig { params(argument: String).returns(T::Boolean) } 57 | def should_skip_argument?(argument) 58 | return true if fixed_dto_params&.key?(argument) 59 | 60 | type.should_skip_argument?(argument, self) 61 | end 62 | 63 | # The field used to define the left side of a relationship. This is the 64 | # field that it will be passed to the next resolver to fetch the data. 65 | sig { returns(T.nilable(Symbol)) } 66 | def reference_id 67 | type.reference_id(self) 68 | end 69 | 70 | sig { returns(T::Boolean) } 71 | def optional? 72 | case type 73 | when ResourceRegistry::RelationshipTypes::HasMany, 74 | ResourceRegistry::RelationshipTypes::HasManyThrough 75 | false 76 | else 77 | optional 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/resource_registry/relationship_type.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module ResourceRegistry 4 | module RelationshipType 5 | extend T::Sig 6 | extend T::Helpers 7 | 8 | InvalidRelationshipSpec = Class.new(StandardError) 9 | 10 | abstract! 11 | 12 | # This enables `is_a?` check type to sorbet 13 | requires_ancestor { Object } 14 | 15 | ResultShape = 16 | T.type_alias do 17 | T::Array[ 18 | T.nilable( 19 | T.any( 20 | T::Hash[String, T.untyped], 21 | T::Array[T::Hash[String, T.untyped]] 22 | ) 23 | ) 24 | ] 25 | end 26 | 27 | sig { params(spec: T::Hash[String, T.untyped]).void } 28 | def initialize(spec) 29 | @spec = spec 30 | validate_spec! 31 | end 32 | 33 | sig { abstract.returns(String) } 34 | def serialize 35 | end 36 | 37 | sig do 38 | abstract 39 | .params(argument: String, relationship: Relationship) 40 | .returns(T::Boolean) 41 | end 42 | def should_skip_argument?(argument, relationship) 43 | end 44 | 45 | sig { abstract.returns(T::Boolean) } 46 | def many_cardinality? 47 | end 48 | 49 | # The field defined to resolve the other side of the relationship, it can be field or primary_key 50 | sig { abstract.params(relationship: Relationship).returns(Symbol) } 51 | def reference_id(relationship) 52 | end 53 | 54 | sig do 55 | abstract 56 | .params( 57 | loaded_data: T::Array[T::Hash[String, T.untyped]], 58 | ids: T.untyped, # FIXME 59 | relationship: Relationship 60 | ) 61 | .returns(ResultShape) 62 | end 63 | def shape_result(loaded_data, ids, relationship) 64 | end 65 | 66 | sig { abstract.returns(Integer) } 67 | def complexity 68 | end 69 | 70 | sig { returns(T::Boolean) } 71 | def forward_entities? 72 | false 73 | end 74 | 75 | sig { returns(T::Boolean) } 76 | # TODO: doc 77 | def forward_selected_fields? 78 | false 79 | end 80 | 81 | sig do 82 | abstract 83 | .params( 84 | dto: T::Hash[Symbol, T.untyped], 85 | ids: T::Array[T.any(String, Integer)], 86 | rel: Relationship, 87 | parent_resource: T.nilable(Resource) 88 | ) 89 | .returns(T::Hash[Symbol, T.untyped]) 90 | end 91 | def prepare_dto(dto, ids, rel, parent_resource) 92 | end 93 | 94 | sig { returns(T::Array[T::Hash[String, T.untyped]]) } 95 | def nested_fields 96 | [] 97 | end 98 | 99 | sig { returns(String) } 100 | def name 101 | @spec["name"] 102 | end 103 | 104 | sig { returns(Symbol) } 105 | def resource_id 106 | @spec["resource_id"]&.to_sym 107 | end 108 | 109 | sig { returns(Symbol) } 110 | def field 111 | @spec["field"]&.to_sym 112 | end 113 | 114 | sig { returns(Symbol) } 115 | def primary_key 116 | @spec["primary_key"]&.to_sym 117 | end 118 | 119 | sig do 120 | overridable 121 | .params(read_dto: T.nilable(T.class_of(T::Struct))) 122 | .returns(T::Boolean) 123 | end 124 | def valid_relationship_field?(read_dto) 125 | return true unless relationship_field_name 126 | 127 | !!read_dto&.props&.keys&.include?(T.must(relationship_field_name)) 128 | end 129 | 130 | sig { overridable.returns(T.nilable(Symbol)) } 131 | def relationship_field_name 132 | end 133 | 134 | private 135 | 136 | sig { void } 137 | def validate_spec! 138 | errors = [] 139 | errors << "name is required" unless @spec["name"] 140 | errors << "resource_id is required" unless @spec["resource_id"] 141 | errors << "field is required" unless @spec["field"] 142 | errors << "primary_key is required" unless @spec["primary_key"] 143 | errors << "name must be a string" unless @spec["name"].is_a?(String) 144 | 145 | return if errors.empty? 146 | 147 | Kernel.raise InvalidRelationshipSpec, 148 | "The given relationship spec is not valid: #{errors.join(", ")}. The spec is: #{@spec}" 149 | end 150 | end 151 | end 152 | -------------------------------------------------------------------------------- /lib/resource_registry/relationship_type_factory.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module ResourceRegistry 4 | module RelationshipTypeFactory 5 | extend T::Sig 6 | 7 | # FIXME: Allow a more dynamic and future proof way to register relationship types 8 | sig { params(spec: T.untyped).returns(RelationshipType) } 9 | def self.from_spec(spec) 10 | type = ResourceRegistry.configuration.relationship_types[spec["type"]] 11 | 12 | raise "Unknown relationship type #{spec}" unless type 13 | 14 | type.new(spec) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/resource_registry/relationship_types/belongs_to.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: strict 3 | 4 | require_relative "../relationship_type" 5 | 6 | module ResourceRegistry 7 | module RelationshipTypes 8 | class BelongsTo 9 | extend T::Sig 10 | 11 | include RelationshipType 12 | 13 | sig { override.returns(String) } 14 | def serialize 15 | "belongs_to" 16 | end 17 | 18 | sig do 19 | override 20 | .params(argument: String, relationship: Relationship) 21 | .returns(T::Boolean) 22 | end 23 | def should_skip_argument?(argument, relationship) 24 | argument == relationship.primary_key.to_s.pluralize 25 | end 26 | 27 | sig { override.returns(T::Boolean) } 28 | def many_cardinality? 29 | false 30 | end 31 | 32 | sig { override.params(relationship: Relationship).returns(Symbol) } 33 | def reference_id(relationship) 34 | relationship.field 35 | end 36 | 37 | sig do 38 | override 39 | .params( 40 | loaded_data: T::Array[T::Hash[String, T.untyped]], 41 | ids: T.untyped, # FIXME 42 | relationship: Relationship 43 | ) 44 | .returns( 45 | T::Array[ 46 | T.nilable( 47 | T.any( 48 | T::Hash[String, T.untyped], 49 | T::Array[T::Hash[String, T.untyped]] 50 | ) 51 | ) 52 | ] 53 | ) 54 | end 55 | def shape_result(loaded_data, ids, relationship) 56 | indexed_res = 57 | loaded_data.index_by do |resource| 58 | resource[relationship.primary_key.to_s] 59 | end 60 | ids.map { |id| indexed_res[id] } 61 | end 62 | 63 | sig { override.returns(Integer) } 64 | def complexity 65 | 5 66 | end 67 | 68 | sig do 69 | override 70 | .params( 71 | dto: T::Hash[Symbol, T.untyped], 72 | ids: T::Array[T.any(String, Integer)], 73 | rel: Relationship, 74 | _parent_resource: T.nilable(Resource) 75 | ) 76 | .returns(T::Hash[Symbol, T.untyped]) 77 | end 78 | def prepare_dto(dto, ids, rel, _parent_resource) 79 | dto[rel.primary_key.to_s.pluralize.to_sym] = ids.compact 80 | dto 81 | end 82 | 83 | sig { override.returns(Symbol) } 84 | def relationship_field_name 85 | primary_key.to_s.pluralize.to_sym 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/resource_registry/relationship_types/has_many.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: strict 3 | 4 | require_relative "../relationship_type" 5 | 6 | module ResourceRegistry 7 | module RelationshipTypes 8 | class HasMany 9 | extend T::Sig 10 | 11 | include RelationshipType 12 | 13 | sig { override.returns(String) } 14 | def serialize 15 | "has_many" 16 | end 17 | 18 | sig do 19 | override 20 | .params(argument: String, relationship: Relationship) 21 | .returns(T::Boolean) 22 | end 23 | def should_skip_argument?(argument, relationship) 24 | argument == relationship.field.to_s.pluralize 25 | end 26 | 27 | sig { override.returns(T::Boolean) } 28 | def many_cardinality? 29 | true 30 | end 31 | 32 | sig { override.params(relationship: Relationship).returns(Symbol) } 33 | def reference_id(relationship) 34 | relationship.primary_key 35 | end 36 | 37 | sig do 38 | override 39 | .params( 40 | loaded_data: T::Array[T::Hash[String, T.untyped]], 41 | ids: T.untyped, # FIXME 42 | relationship: Relationship 43 | ) 44 | .returns( 45 | T::Array[ 46 | T.nilable( 47 | T.any( 48 | T::Hash[String, T.untyped], 49 | T::Array[T::Hash[String, T.untyped]] 50 | ) 51 | ) 52 | ] 53 | ) 54 | end 55 | def shape_result(loaded_data, ids, relationship) 56 | indexed_res = loaded_data.group_by { |r| r[relationship.field.to_s] } 57 | ids.map { |id| indexed_res[id] || [] } 58 | end 59 | 60 | sig { override.returns(Integer) } 61 | def complexity 62 | 10 63 | end 64 | 65 | sig do 66 | override 67 | .params( 68 | dto: T::Hash[Symbol, T.untyped], 69 | ids: T::Array[T.any(String, Integer)], 70 | rel: Relationship, 71 | _parent_resource: T.nilable(Resource) 72 | ) 73 | .returns(T::Hash[Symbol, T.untyped]) 74 | end 75 | def prepare_dto(dto, ids, rel, _parent_resource) 76 | dto[rel.field.to_s.pluralize.to_sym] = ids.compact 77 | dto 78 | end 79 | 80 | sig { override.returns(Symbol) } 81 | def relationship_field_name 82 | field.to_s.pluralize.to_sym 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/resource_registry/relationship_types/has_many_through.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: strict 3 | 4 | require_relative "../relationship_type" 5 | 6 | module ResourceRegistry 7 | module RelationshipTypes 8 | class HasManyThrough 9 | extend T::Sig 10 | 11 | include RelationshipType 12 | 13 | sig { override.returns(String) } 14 | def serialize 15 | "has_many_through" 16 | end 17 | 18 | sig do 19 | override 20 | .params(argument: String, relationship: Relationship) 21 | .returns(T::Boolean) 22 | end 23 | def should_skip_argument?(argument, relationship) 24 | argument == relationship.primary_key.to_s.pluralize 25 | end 26 | 27 | sig { override.returns(T::Boolean) } 28 | def many_cardinality? 29 | true 30 | end 31 | 32 | sig { override.params(relationship: Relationship).returns(Symbol) } 33 | def reference_id(relationship) 34 | relationship.field 35 | end 36 | 37 | sig do 38 | override 39 | .params( 40 | loaded_data: T::Array[T::Hash[String, T.untyped]], 41 | ids: T.untyped, # FIXME 42 | _relationship: Relationship 43 | ) 44 | .returns( 45 | T::Array[ 46 | T.nilable( 47 | T.any( 48 | T::Hash[String, T.untyped], 49 | T::Array[T::Hash[String, T.untyped]] 50 | ) 51 | ) 52 | ] 53 | ) 54 | end 55 | def shape_result(loaded_data, ids, _relationship) 56 | ids.map do |many_ids| 57 | loaded_data.filter { |r| many_ids.include?(r["id"]) } 58 | end 59 | end 60 | 61 | sig { override.returns(Integer) } 62 | def complexity 63 | 10 64 | end 65 | 66 | sig do 67 | override 68 | .params( 69 | dto: T::Hash[Symbol, T.untyped], 70 | ids: T::Array[T.any(String, Integer)], 71 | _rel: Relationship, 72 | _parent_resource: T.nilable(Resource) 73 | ) 74 | .returns(T::Hash[Symbol, T.untyped]) 75 | end 76 | def prepare_dto(dto, ids, _rel, _parent_resource) 77 | dto[:ids] = ids.flatten.compact.uniq 78 | dto 79 | end 80 | 81 | sig { override.returns(Symbol) } 82 | def relationship_field_name 83 | :ids 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/resource_registry/relationship_types/has_one.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: strict 3 | 4 | require_relative "../relationship_type" 5 | 6 | module ResourceRegistry 7 | module RelationshipTypes 8 | class HasOne 9 | extend T::Sig 10 | 11 | include RelationshipType 12 | 13 | sig { override.returns(String) } 14 | def serialize 15 | "has_one" 16 | end 17 | 18 | sig do 19 | override 20 | .params(argument: String, relationship: Relationship) 21 | .returns(T::Boolean) 22 | end 23 | def should_skip_argument?(argument, relationship) 24 | argument == relationship.field.to_s.pluralize 25 | end 26 | 27 | sig { override.returns(T::Boolean) } 28 | def many_cardinality? 29 | false 30 | end 31 | 32 | # FIXME: Review if this is actually reference_id 33 | sig { override.params(relationship: Relationship).returns(Symbol) } 34 | def reference_id(relationship) 35 | relationship.primary_key 36 | end 37 | 38 | sig do 39 | override 40 | .params( 41 | loaded_data: T::Array[T::Hash[String, T.untyped]], 42 | ids: T.untyped, # FIXME 43 | relationship: Relationship 44 | ) 45 | .returns( 46 | T::Array[ 47 | T.nilable( 48 | T.any( 49 | T::Hash[String, T.untyped], 50 | T::Array[T::Hash[String, T.untyped]] 51 | ) 52 | ) 53 | ] 54 | ) 55 | end 56 | def shape_result(loaded_data, ids, relationship) 57 | indexed_res = 58 | loaded_data.index_by { |resource| resource[relationship.field.to_s] } 59 | ids.map { |id| indexed_res[id] } 60 | end 61 | 62 | sig { override.returns(Integer) } 63 | def complexity 64 | 5 65 | end 66 | 67 | sig do 68 | override 69 | .params( 70 | dto: T::Hash[Symbol, T.untyped], 71 | ids: T::Array[T.any(String, Integer)], 72 | rel: Relationship, 73 | _parent_resource: T.nilable(Resource) 74 | ) 75 | .returns(T::Hash[Symbol, T.untyped]) 76 | end 77 | def prepare_dto(dto, ids, rel, _parent_resource) 78 | dto[rel.field.to_s.pluralize.to_sym] = ids.compact 79 | dto 80 | end 81 | 82 | sig { override.returns(Symbol) } 83 | def relationship_field_name 84 | field.to_s.pluralize.to_sym 85 | end 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/resource_registry/repositories/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: false 3 | 4 | require_relative "../../runtime_generic" 5 | require_relative "../serializer" 6 | 7 | module ResourceRegistry 8 | module Repositories 9 | module Base 10 | extend T::Sig 11 | extend T::Helpers 12 | extend T::Generic 13 | # CAUTION This is not supported by sorbet, consider using T::Generic 14 | # instead if yo don't need to preserve generic runtime information 15 | extend RuntimeGeneric 16 | 17 | include Kernel 18 | 19 | abstract! 20 | 21 | Entity = type_member { { upper: T::Struct } } 22 | 23 | sig { returns(T.untyped) } 24 | def self.entity 25 | T.unsafe(const_get(:Entity)).inner_type[:fixed] 26 | end 27 | 28 | sig do 29 | overridable 30 | .params(dto: T.untyped, context: T.untyped) 31 | .returns(T.untyped) 32 | end 33 | def read(dto:, context:) 34 | raise_error(__method__) 35 | end 36 | 37 | sig do 38 | overridable 39 | .params(entity: Entity, tags: T::Set[T.untyped]) 40 | .returns(T::Hash[Symbol, T.untyped]) 41 | end 42 | def serialize(entity:, tags: []) 43 | serializer.serialize(entity: entity, tags: tags) 44 | end 45 | 46 | sig(:final) { returns(String) } 47 | def self.namespace 48 | namespace = to_s.split("::Repositories").first 49 | return T.must(namespace).sub("::", "") if namespace != to_s 50 | 51 | T.must(to_s.split("::").first) 52 | end 53 | 54 | sig(:final) { returns(String) } 55 | def self.resource_name 56 | T.must(to_s.split("::").last) 57 | end 58 | 59 | private 60 | 61 | sig { returns(ResourceRegistry::Serializer) } 62 | def serializer 63 | @serializer ||= 64 | T.let( 65 | ResourceRegistry::Serializer.new( 66 | resource: T.must(self.class.resource) 67 | ), 68 | T.nilable(ResourceRegistry::Serializer) 69 | ) 70 | end 71 | 72 | sig { params(method: T.nilable(Symbol)).returns(T.noreturn) } 73 | def raise_error(method) 74 | raise NotImplementedError, 75 | "#{method} must be implemented in #{self.class} repository" 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/resource_registry/serializer.rb: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | module ResourceRegistry 4 | class Serializer 5 | extend T::Sig 6 | 7 | # Maybe this should be a generic? 8 | sig { params(resource: Resource).void } 9 | def initialize(resource:) 10 | @resource = resource 11 | end 12 | 13 | sig do 14 | params(entity: T::Struct, tags: T::Set[Symbol]).returns( 15 | T::Hash[Symbol, T.untyped] 16 | ) 17 | end 18 | def serialize(entity:, tags:) 19 | entity_methods = Set.new(entity.methods) 20 | 21 | resource_schema 22 | .properties 23 | .each_with_object({}) do |property, acc| 24 | # If property has serialization groups and they are not included in the tags this property is skipped 25 | if property.serialization_groups.any? && 26 | (property.serialization_groups & tags).none? 27 | next 28 | end 29 | 30 | property_value = 31 | if entity_methods.include?(property.name.to_sym) 32 | entity.send(property.name) 33 | elsif property.resolvable && 34 | (property.resolver&.values&.size || 0) > 1 35 | # Review all this mess with resolvers 36 | property 37 | .resolver 38 | &.map { |_, value| entity.send(value) } 39 | &.join(" ") 40 | else 41 | next 42 | end 43 | 44 | acc[property.name.to_sym] = recursive_serialization(property_value) 45 | end 46 | end 47 | 48 | private 49 | 50 | sig { params(property_value: T.untyped).returns(T.untyped) } 51 | def recursive_serialization(property_value) 52 | if property_value.is_a?(Array) 53 | return property_value.map { |pv| recursive_serialization(pv) } 54 | end 55 | 56 | if property_value.respond_to?(:serialize, false) 57 | property_value.serialize 58 | else 59 | property_value 60 | end 61 | end 62 | 63 | sig { returns(SchemaRegistry::Schema) } 64 | def resource_schema 65 | @resource.schema 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/resource_registry/verb.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: strict 3 | 4 | require_relative "../schema_registry/schema" 5 | 6 | module ResourceRegistry 7 | # This is the representation of a verb over a resource. Each resource can 8 | # have multiple verbs that should be exposed by its repository 9 | class Verb < T::Struct 10 | extend T::Sig 11 | 12 | DtoClassNotFound = Class.new(StandardError) 13 | 14 | const :id, Symbol 15 | const :dto_raw, String 16 | const :summary, T.nilable(String), default: nil 17 | const :description, T.nilable(String), default: nil 18 | const :deprecated_on, T.nilable(Date), default: nil 19 | const :webhook_description, T.nilable(String), default: nil 20 | const :schema, SchemaRegistry::Schema 21 | const :return_many, T::Boolean, default: false 22 | 23 | sig { returns(Symbol) } 24 | def schema_identifier 25 | @schema_identifier ||= 26 | T.let(:"#{id.to_s.underscore}_dto", T.nilable(Symbol)) 27 | end 28 | 29 | sig { returns(T::Boolean) } 30 | def deprecated? 31 | return false if deprecated_on.blank? 32 | 33 | T.must(deprecated_on) < Time.zone.today 34 | end 35 | 36 | sig { returns(T::Boolean) } 37 | def mutation? 38 | destroy? || update? || create? 39 | end 40 | 41 | sig { returns(T::Boolean) } 42 | def get? 43 | %i[find show read].include? id 44 | end 45 | 46 | sig { returns(T::Boolean) } 47 | def destroy? 48 | id == :delete 49 | end 50 | 51 | sig { returns(T::Boolean) } 52 | def update? 53 | id == :update 54 | end 55 | 56 | sig { returns(T::Boolean) } 57 | def create? 58 | id == :create 59 | end 60 | 61 | sig { returns(T.class_of(T::Struct)) } 62 | def dto 63 | dto_klass = dto_raw.safe_constantize 64 | 65 | if dto_klass.nil? 66 | raise DtoClassNotFound, "DTO class #{dto_raw} for verb #{id} not found" 67 | end 68 | 69 | dto_klass 70 | end 71 | 72 | sig { returns(T::Hash[Symbol, T.untyped]) } 73 | def dump 74 | {}.tap do |result| 75 | result["id"] = id 76 | result["dto"] = dto.to_s 77 | result["schema"] = schema.dump 78 | result["return_many"] = return_many 79 | end 80 | end 81 | 82 | sig { params(spec: T.untyped).returns(Verb) } 83 | def self.load(spec) 84 | id = spec["id"]&.to_sym 85 | raise ArgumentError, "Missing verb ID: #{id}" if id.nil? 86 | 87 | dto = spec["dto"] 88 | raise ArgumentError, "DTO for verb #{id} not found" if dto.nil? 89 | 90 | new( 91 | id: id, 92 | dto_raw: dto, 93 | schema: SchemaRegistry::Schema.load(spec["schema"]), 94 | summary: spec["summary"], 95 | return_many: spec["return_many"], 96 | description: spec["description"], 97 | webhook_description: spec["webhook_description"] 98 | ) 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/resource_registry/versions.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module ResourceRegistry 4 | class Versions 5 | extend T::Sig 6 | extend T::Helpers 7 | 8 | sig { params(versions: T::Array[Version]).void } 9 | def initialize(versions:) 10 | @versions = versions 11 | end 12 | 13 | sig { params(name: T.nilable(String)).returns(T.nilable(Version)) } 14 | def find(name) 15 | return if name.blank? 16 | 17 | versions.find { |version| version.matches?(name) } 18 | end 19 | 20 | sig { params(name: String).returns(Version) } 21 | def find!(name) 22 | find(name) || raise("Version '#{name}' not found") 23 | end 24 | 25 | sig do 26 | params(name_or_version: T.any(String, Version)).returns( 27 | T.nilable(Version) 28 | ) 29 | end 30 | def find_next(name_or_version) 31 | version = 32 | name_or_version.is_a?(String) ? find!(name_or_version) : name_or_version 33 | index = T.must(sorted_versions.index(version)) 34 | 35 | sorted_versions[index + 1] 36 | end 37 | 38 | sig { returns(T::Array[Version]) } 39 | def sorted_versions 40 | versions.sort 41 | end 42 | 43 | sig do 44 | params(from: T.nilable(String), to: T.nilable(String)).returns( 45 | T::Array[ResourceRegistry::Versions::Version] 46 | ) 47 | end 48 | def in_range(from, to) 49 | from = find!(from) unless from.nil? 50 | to = find!(to) unless to.nil? 51 | versions.select do |version| 52 | (from.nil? || version >= from) && (to.nil? || version <= to) 53 | end 54 | end 55 | 56 | private 57 | 58 | sig { returns(T::Array[Version]) } 59 | attr_reader :versions 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/resource_registry/versions/version.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module ResourceRegistry 4 | class Versions 5 | class Version 6 | extend T::Sig 7 | 8 | sig do 9 | params( 10 | name: String, 11 | aliases: T.nilable(T.any(String, T::Array[String])) 12 | ).void 13 | end 14 | def initialize(name, aliases: nil) 15 | @name = name 16 | @aliases = T.let(Array(aliases), T::Array[String]) 17 | end 18 | 19 | sig { returns(String) } 20 | attr_reader :name 21 | 22 | sig { returns(T::Array[String]) } 23 | attr_reader :aliases 24 | 25 | sig { returns(String) } 26 | def to_s 27 | name 28 | end 29 | 30 | sig { params(str: String).returns(T::Boolean) } 31 | def matches?(str) 32 | [name, *aliases].include?(str) 33 | end 34 | 35 | sig { params(other: Version).returns(T.nilable(Integer)) } 36 | def <=>(other) 37 | name <=> other.name 38 | end 39 | 40 | sig { params(other: Version).returns(T::Boolean) } 41 | def >=(other) 42 | name >= other.name 43 | end 44 | 45 | sig { params(other: Version).returns(T::Boolean) } 46 | def <=(other) 47 | name <= other.name 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/runtime_generic.rb: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # This module allows using type introspection to serialize/deserialize custom generics in 4 | # T::Structs. 5 | # 6 | # While Sorbet can answer questions about types in T::Structs and arrays, it erases generic types 7 | # at runtime. To support custom generics, we must manually preserve the type information with 8 | # which they were declared. 9 | # 10 | # Use this module instead of `T::Generic` to create generic types with runtime type information. 11 | module RuntimeGeneric 12 | extend T::Helpers 13 | extend T::Sig 14 | include T::Generic 15 | 16 | class TypedGeneric < T::Types::Simple 17 | extend T::Sig 18 | 19 | def name 20 | "#{raw_type.name}[#{inner_type.name}]" 21 | end 22 | 23 | def initialize(raw_type, inner_type) 24 | super(raw_type) 25 | @inner_type = inner_type 26 | end 27 | 28 | attr_reader :inner_type 29 | end 30 | 31 | class MyTypeMember < T::Types::TypeMember 32 | def initialize(variance, &type_proc) 33 | super(variance) 34 | @type_proc = type_proc 35 | end 36 | 37 | def inner_type 38 | @inner_type ||= @type_proc.call 39 | end 40 | end 41 | 42 | def [](inner_type) 43 | RuntimeGeneric::TypedGeneric.new(self, inner_type) 44 | end 45 | 46 | def type_member(variance = :invariant, &blk) 47 | if defined?(Tapioca::TypeVariableModule) 48 | # `T::Generic#type_member` just instantiates a `T::Type::TypeMember` instance and returns it. 49 | # We use that when registering the type member and then later return it from this method. 50 | return( 51 | Tapioca::TypeVariableModule 52 | .new( 53 | T.cast(self, Module), 54 | Tapioca::TypeVariableModule::Type::Member, 55 | variance, 56 | blk 57 | ) 58 | .tap do |type_variable| 59 | Tapioca::Runtime::GenericTypeRegistry.register_type_variable( 60 | self, 61 | type_variable 62 | ) 63 | end 64 | ) 65 | end 66 | 67 | MyTypeMember.new(variance, &blk) 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/schema_registry/README.md: -------------------------------------------------------------------------------- 1 | # Schema registry 2 | 3 | A json-schema based registry 4 | 5 | ## Features 6 | 7 | - Versioning 8 | - Unneeded version blocking through CI/CD 9 | - Required version blocking through CI/CD 10 | - Lifecycle / States: Dead, Stable, Next (alpha/beta) 11 | - `schema_registry version:bump employee` 12 | - Track version usage to clean up unused deprecated versions 13 | - Typing (?) -> Sorbet 14 | - Validation 15 | 16 | ## What is an schema? 17 | 18 | - Metadata (generated files, similar to RBI generation) 19 | 20 | - Last stable json-schema 21 | - Last unstable tag 22 | 23 | - Schema itself (json-schema) 24 | 25 | ## Adding a new schema 26 | 27 | Just drop a json-schema formatted in YAML into your-component/app/schemas 28 | folder and the SchemaRegistry system will load it automatically into the 29 | registry for you. 30 | 31 | ## Generating RBIs from schemas 32 | 33 | ```bash 34 | bin/rails schema_registry:generate_rbis 35 | ``` 36 | 37 | ## Generate a json-schema representation of a T::Struct 38 | 39 | This is specially useful when you need to define the initial version of a 40 | json-schema for an entity or DTO since both are usually defined with 41 | `T::Struct`. 42 | 43 | ```bash 44 | bin/rails "schema_registry:generate_schema_from_struct[Ats::Entities::JobPosting]" 45 | ``` 46 | 47 | ## What abstractions can use schemas? 48 | 49 | - DTOs (Repositories parameters shape) 50 | - Entities 51 | - Events 52 | 53 | ## To research 54 | 55 | - [JSON-ld](https://json-ld.org/) 56 | -------------------------------------------------------------------------------- /lib/schema_registry/filter_field.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module SchemaRegistry 4 | class FilterField < T::Struct 5 | extend T::Sig 6 | 7 | const :name, String 8 | const :resolver, T.nilable(T::Hash[Symbol, String]) 9 | const :type, PropertyType 10 | const :in_memory, T::Boolean, default: false 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/schema_registry/json_schema_mapper.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module SchemaRegistry 4 | class JsonSchemaMapper 5 | extend T::Sig 6 | 7 | class UnrecognizedJsonSchemaTypeError < StandardError 8 | end 9 | 10 | DATE_FIELDS = T.let(%w[date-time date].freeze, T::Array[String]) 11 | 12 | # TODO: Proper type json-schema 13 | sig { params(namespace: String, definition: T.untyped).void } 14 | def initialize(namespace:, definition:) 15 | @namespace = namespace 16 | @definition = definition 17 | end 18 | 19 | sig { returns(Schema) } 20 | def call 21 | key = definition.keys.first 22 | 23 | properties = 24 | handle_properties( 25 | definition[key]["properties"], 26 | definition[key]["required"] 27 | ) 28 | 29 | Schema.new( 30 | name: key, 31 | namespace: namespace, 32 | properties: properties || [], 33 | raw_json_schema: definition, 34 | additional_filter_fields: 35 | handle_fields(definition[key]["additional_filter_fields"]) 36 | ) 37 | end 38 | 39 | private 40 | 41 | sig { returns(T.untyped) } 42 | attr_reader :definition 43 | 44 | sig { returns(String) } 45 | attr_reader :namespace 46 | 47 | sig do 48 | params(items: T.nilable(T::Hash[T.untyped, T.untyped])).returns( 49 | T::Array[SchemaRegistry::Property] 50 | ) 51 | end 52 | def handle_items(items) 53 | return [] if items.nil? 54 | 55 | types = handle_types(items["type"], items["format"]) 56 | 57 | [ 58 | SchemaRegistry::Property.new( 59 | name: "", 60 | types: types, 61 | type_name: items["typedef"].to_s, 62 | items: handle_items(items["items"]), 63 | enum_values: items.dig("items", "enum") || items["enum"], 64 | default: items["default"], 65 | properties: 66 | handle_properties(items["properties"], items["required"]) || [], 67 | required: types.exclude?(SchemaRegistry::PropertyType::Null) 68 | ) 69 | ] 70 | end 71 | 72 | sig do 73 | params(input: T.nilable(T::Array[T::Hash[String, T.untyped]])).returns( 74 | T::Array[FilterField] 75 | ) 76 | end 77 | def handle_fields(input) 78 | return [] if input.nil? 79 | 80 | input.map do |obj| 81 | FilterField.new( 82 | name: obj["name"], 83 | resolver: handle_resolver(obj["resolver"] || obj["field"]), 84 | type: type_to_sorbet(obj["type"], nil), 85 | in_memory: obj["in_memory"] == true 86 | ) 87 | end 88 | end 89 | 90 | sig do 91 | params( 92 | properties: T.nilable(T::Hash[T.untyped, T.untyped]), 93 | required: T.nilable(T::Array[String]) 94 | ).returns(T.nilable(T::Array[SchemaRegistry::Property])) 95 | end 96 | def handle_properties(properties, required) 97 | properties&.map do |key, value| 98 | enum_values = 99 | (value.dig("items", "enum") || value["enum"] || []).map do |v| 100 | next v if v.is_a?(String) 101 | 102 | v.serialize 103 | end 104 | 105 | default = 106 | if enum_values.any? && value["default"].kind_of?(Array) 107 | # Handle arrays 108 | value["default"]&.map do |v| 109 | next v if v.is_a?(String) 110 | 111 | v.serialize 112 | end 113 | else 114 | value["default"] 115 | end 116 | 117 | SchemaRegistry::Property.new( 118 | name: key, 119 | types: handle_types(value["type"], value["format"]), 120 | type_name: value["typedef"].to_s, 121 | items: handle_items(value["items"]), 122 | example: value["example"], 123 | description: value["description"], 124 | enum_values: enum_values, 125 | default: default, 126 | resolver: handle_resolver(value["resolver"]), 127 | resolvable: !value["resolvable"].nil? && value["resolvable"], 128 | properties: 129 | handle_properties(value["properties"], value["required"]) || [], 130 | required: required ? required.include?(key) : false, 131 | serialization_groups: 132 | handle_serialization_groups(value["serialization_groups"]) 133 | ) 134 | end 135 | end 136 | 137 | sig do 138 | params( 139 | prop_type: T.any(String, T::Array[String]), 140 | format: T.nilable(String) 141 | ).returns(T::Array[PropertyType]) 142 | end 143 | def handle_types(prop_type, format) 144 | Array(prop_type).map { |type| type_to_sorbet(type, format) } 145 | end 146 | 147 | sig do 148 | params(prop_type: T.any(String, T::Array[String])).returns(T::Boolean) 149 | end 150 | def nilable?(prop_type) 151 | return false unless prop_type.is_a?(Array) 152 | 153 | prop_type.include?("null") 154 | end 155 | 156 | sig do 157 | params( 158 | resolver: T.any(NilClass, String, T::Hash[Symbol, String]) 159 | ).returns(T.nilable(T::Hash[Symbol, String])) 160 | end 161 | def handle_resolver(resolver) 162 | if resolver.is_a?(String) 163 | { all: resolver } 164 | elsif resolver.present? 165 | resolver.symbolize_keys 166 | end 167 | end 168 | 169 | sig do 170 | params(serialization_groups: T.any(NilClass, T::Array[String])).returns( 171 | T::Set[Symbol] 172 | ) 173 | end 174 | def handle_serialization_groups(serialization_groups) 175 | return Set[] if serialization_groups.blank? 176 | 177 | serialization_groups.compact.to_set(&:to_sym) 178 | end 179 | 180 | sig do 181 | params( 182 | json_schema_type: T.nilable(String), 183 | format: T.nilable(String) 184 | ).returns(PropertyType) 185 | end 186 | def type_to_sorbet(json_schema_type, format) 187 | case json_schema_type 188 | when "string" 189 | string_format_to_sorbet(format) 190 | when "number" 191 | PropertyType::Number 192 | when "integer" 193 | if format == "big" 194 | PropertyType::BigInteger 195 | else 196 | PropertyType::Integer 197 | end 198 | when "boolean" 199 | PropertyType::Boolean 200 | when "null" 201 | PropertyType::Null 202 | when "array" 203 | PropertyType::Array 204 | when "object" 205 | PropertyType::Object 206 | else 207 | raise UnrecognizedJsonSchemaTypeError, 208 | "unrecognized type #{json_schema_type}" 209 | end 210 | end 211 | 212 | sig { params(format: T.nilable(String)).returns(PropertyType) } 213 | def string_format_to_sorbet(format) 214 | case format 215 | when "date-time" 216 | PropertyType::DateTime 217 | when "time" 218 | PropertyType::Time 219 | when "date" 220 | PropertyType::Date 221 | when "duration" 222 | PropertyType::Duration 223 | when "email" 224 | PropertyType::Email 225 | when "uri" 226 | PropertyType::Uri 227 | when "regex" 228 | PropertyType::Regex 229 | when "binary" 230 | PropertyType::File 231 | else 232 | PropertyType::String 233 | end 234 | end 235 | end 236 | end 237 | -------------------------------------------------------------------------------- /lib/schema_registry/maybe.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | # frozen_string_literal: true 3 | 4 | require_relative("../runtime_generic") 5 | require_relative("maybe/absent") 6 | require_relative("maybe/present") 7 | 8 | # Represents an instance of an object that may or may not be present. This can be useful in certain 9 | # cases where `nil` represents a valid value instead of an absent value, i.e. update DTOs. 10 | # 11 | # An useful way to think about `Maybe` is as a collection, like `Array` or `Set` but that can only 12 | # hold a maximum of 1 elements at a time. 13 | module Maybe 14 | extend T::Sig 15 | extend T::Helpers 16 | extend RuntimeGeneric 17 | include Kernel 18 | interface! 19 | # NOTE: Beware of implementing a `Maybe#value` method in the interface so you can call it without 20 | # type safety. >:( 21 | 22 | Value = type_member(:out) { { upper: BasicObject } } 23 | 24 | sig do 25 | type_parameters(:Key) 26 | .params(input: T::Hash[T.type_parameter(:Key), T.untyped]) 27 | .returns(T::Hash[T.type_parameter(:Key), T.untyped]) 28 | end 29 | # You can use this method to easily transform a Hash with `Maybe` values into one without them, 30 | # filtering out `Maybe` instances that are empty and unwrapping the present ones. 31 | # 32 | # Given a hash containing `Maybe` instances, returns a hash with only the values that are present. 33 | # It also unwraps the present values. 34 | # 35 | # For convenience, it also recursively serializes nested T::Structs and strips nested hashes, 36 | # arrays and sets. 37 | # 38 | # ```ruby 39 | # Maybe.strip({ a: Maybe.from(1), b: Maybe.empty, c: Maybe.from(3) }) 40 | # # => { a: 1, c: 3 } 41 | # ``` 42 | def self.strip(input) # rubocop:disable Metrics/PerceivedComplexity 43 | input 44 | .reject { |_key, value| value == Maybe.empty } 45 | .to_h do |key, value| 46 | unwrapped = value.is_a?(Maybe::Present) ? value.value : value 47 | enumerated = 48 | if unwrapped.is_a?(Array) || unwrapped.is_a?(Set) 49 | unwrapped.map do |v| 50 | v.is_a?(T::Struct) ? Maybe.strip(v.serialize) : Maybe.strip(v) 51 | end 52 | else 53 | unwrapped 54 | end 55 | serialized = 56 | enumerated.is_a?(T::Struct) ? enumerated.serialize : enumerated 57 | stripped = serialized.is_a?(Hash) ? Maybe.strip(serialized) : serialized 58 | 59 | [key, stripped] 60 | end 61 | end 62 | 63 | sig { returns(Absent) } 64 | # Creates an empty instance. 65 | def self.empty 66 | Absent.new 67 | end 68 | 69 | sig { returns(Absent) } 70 | # Creates an empty instance. 71 | # Alias for self.empty 72 | def self.none 73 | empty 74 | end 75 | 76 | sig { returns(Absent) } 77 | # Creates an empty instance. 78 | # Alias for self.empty 79 | def self.absent 80 | empty 81 | end 82 | 83 | sig do 84 | type_parameters(:Value) 85 | .params(value: T.all(BasicObject, T.type_parameter(:Value))) 86 | .returns(Maybe[T.all(BasicObject, T.type_parameter(:Value))]) 87 | end 88 | # Creates an instance containing the specified value. 89 | # Necessary to make this work with sorbet-coerce 90 | def self.new(value) 91 | from(value) 92 | end 93 | 94 | sig do 95 | type_parameters(:Value) 96 | .params(value: T.all(BasicObject, T.type_parameter(:Value))) 97 | .returns(Maybe[T.all(BasicObject, T.type_parameter(:Value))]) 98 | end 99 | # Creates an instance containing the specified value. 100 | def self.from(value) 101 | Present[T.all(BasicObject, T.type_parameter(:Value))].new(value) 102 | end 103 | 104 | sig { abstract.returns(T::Boolean) } 105 | # `true` if this `Maybe` contains a value, `false` otherwise. 106 | def present? 107 | end 108 | 109 | sig { abstract.returns(T::Boolean) } 110 | # `true` if this `Maybe` does not contain a value, `false` otherwise. 111 | def absent? 112 | end 113 | 114 | sig { abstract.returns(T::Boolean) } 115 | # `true` if this `Maybe` does not contain a value, `false` otherwise. 116 | # 117 | # alias of `#absent` 118 | def empty? 119 | end 120 | 121 | sig do 122 | abstract 123 | .type_parameters(:Default) 124 | .params(default: T.type_parameter(:Default)) 125 | .returns(T.any(Value, T.type_parameter(:Default))) 126 | end 127 | # Returns the value if there's one, else, it returns the provided default. 128 | def or_default(default) 129 | end 130 | 131 | sig do 132 | abstract 133 | .type_parameters(:Return) 134 | .params( 135 | _block: T.proc.params(v: Value).returns(T.type_parameter(:Return)) 136 | ) 137 | .returns(T.nilable(T.type_parameter(:Return))) 138 | end 139 | # Executes the given code block if there's a value present, the code block will receive the value 140 | # as an argument and the method will return whatever the code block returns or `nil` if no value 141 | # present. 142 | def when_present(&_block) 143 | end 144 | 145 | sig do 146 | abstract 147 | .type_parameters(:Return) 148 | .params(_block: T.proc.returns(T.type_parameter(:Return))) 149 | .returns(T.nilable(T.type_parameter(:Return))) 150 | end 151 | # Executes the given code block if a value isn't present, returns whatever the code block returned 152 | # or `nil` if if the value was present. 153 | def when_absent(&_block) 154 | end 155 | 156 | sig do 157 | abstract 158 | .params(_block: T.proc.params(value: Value).returns(T::Boolean)) 159 | .returns(Maybe[Value]) 160 | end 161 | # Evaluate the specified block passing it the value if one is present, if the block returns true, 162 | # returns an instance containing the same value, if the block returns false, returns an empty 163 | # instance. If the instance is already empty, it returns an empty instance and the block is not 164 | # evaluated. 165 | # 166 | # This is analogous to the `Array#filter` method if the `Maybe` class were an `Array` that can 167 | # hold one element at maximum. 168 | def filter(&_block) 169 | end 170 | 171 | sig do 172 | abstract 173 | .type_parameters(:Default) 174 | .params( 175 | _block: 176 | T 177 | .proc 178 | .params(value: Value) 179 | .returns(T.all(BasicObject, T.type_parameter(:Default))) 180 | ) 181 | .returns(Maybe[T.all(BasicObject, T.type_parameter(:Default))]) 182 | end 183 | # Evaluate the specified block passing it the value if one is present, returns an instance 184 | # containing the result of the evaluation. If no element is present, returns an empty instance. 185 | # 186 | # This is analogous to the `Array#map` method if the `Maybe` class were an `Array` that can 187 | # hold one element at maximum. 188 | def map(&_block) 189 | end 190 | end 191 | -------------------------------------------------------------------------------- /lib/schema_registry/maybe/absent.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | # frozen_string_literal: true 3 | 4 | module Maybe 5 | # Class used to represent the empty case 6 | class Absent 7 | extend T::Sig 8 | extend T::Generic 9 | include Maybe 10 | # final! FIXME 11 | 12 | Value = type_member { { fixed: T.noreturn } } 13 | 14 | sig(:final) { override.returns(FalseClass) } 15 | def present? 16 | false 17 | end 18 | 19 | sig(:final) { override.returns(TrueClass) } 20 | def absent? 21 | true 22 | end 23 | 24 | sig(:final) { override.returns(TrueClass) } 25 | def empty? 26 | absent? 27 | end 28 | 29 | sig(:final) do 30 | override 31 | .type_parameters(:Default) 32 | .params(default: T.type_parameter(:Default)) 33 | .returns(T.type_parameter(:Default)) 34 | end 35 | def or_default(default) 36 | default 37 | end 38 | 39 | sig(:final) do 40 | override 41 | .type_parameters(:Return) 42 | .params( 43 | _block: T.proc.params(v: Value).returns(T.type_parameter(:Return)) 44 | ) 45 | .returns(T.nilable(T.type_parameter(:Return))) 46 | end 47 | def when_present(&_block) 48 | nil 49 | end 50 | 51 | sig(:final) do 52 | override 53 | .type_parameters(:Return) 54 | .params(_block: T.proc.returns(T.type_parameter(:Return))) 55 | .returns(T.nilable(T.type_parameter(:Return))) 56 | end 57 | def when_absent(&_block) 58 | yield 59 | end 60 | 61 | sig(:final) do 62 | override 63 | .params(_block: T.proc.params(value: Value).returns(T::Boolean)) 64 | .returns(Maybe[Value]) 65 | end 66 | def filter(&_block) 67 | self 68 | end 69 | 70 | sig(:final) do 71 | override 72 | .type_parameters(:Default) 73 | .params( 74 | _block: 75 | T.proc.params(value: Value).returns(T.type_parameter(:Default)) 76 | ) 77 | .returns(Maybe[T.all(BasicObject, T.type_parameter(:Default))]) 78 | end 79 | def map(&_block) 80 | self 81 | end 82 | 83 | sig(:final) { override.params(other: BasicObject).returns(T::Boolean) } 84 | def ==(other) 85 | self.class === other # rubocop:disable Style/CaseEquality 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/schema_registry/maybe/present.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | # frozen_string_literal: true 3 | 4 | module Maybe 5 | # Class used to represent the case when a value is available 6 | class Present 7 | extend T::Sig 8 | extend T::Generic 9 | include Maybe 10 | # final! FIXME 11 | 12 | Value = type_member(:out) { { upper: BasicObject } } 13 | 14 | sig(:final) { params(value: Value).void } 15 | def initialize(value) 16 | @value = value 17 | end 18 | 19 | sig(:final) { override.returns(TrueClass) } 20 | def present? 21 | true 22 | end 23 | 24 | sig(:final) { override.returns(FalseClass) } 25 | def absent? 26 | false 27 | end 28 | 29 | sig(:final) { override.returns(FalseClass) } 30 | def empty? 31 | absent? 32 | end 33 | 34 | sig(:final) do 35 | override 36 | .type_parameters(:Default) 37 | .params(_default: T.type_parameter(:Default)) 38 | .returns(Value) 39 | end 40 | def or_default(_default) 41 | value 42 | end 43 | 44 | sig(:final) do 45 | override 46 | .type_parameters(:Return) 47 | .params( 48 | _block: T.proc.params(v: Value).returns(T.type_parameter(:Return)) 49 | ) 50 | .returns(T.nilable(T.type_parameter(:Return))) 51 | end 52 | def when_present(&_block) 53 | yield value 54 | end 55 | 56 | sig(:final) do 57 | override 58 | .type_parameters(:Return) 59 | .params(_block: T.proc.returns(T.type_parameter(:Return))) 60 | .returns(T.nilable(T.type_parameter(:Return))) 61 | end 62 | def when_absent(&_block) 63 | nil 64 | end 65 | 66 | sig(:final) do 67 | override 68 | .params(_block: T.proc.params(value: Value).returns(T::Boolean)) 69 | .returns(Maybe[Value]) 70 | end 71 | def filter(&_block) 72 | return self if yield value 73 | 74 | Absent.new 75 | end 76 | 77 | sig(:final) do 78 | override 79 | .type_parameters(:Default) 80 | .params( 81 | _block: 82 | T 83 | .proc 84 | .params(value: Value) 85 | .returns(T.all(BasicObject, T.type_parameter(:Default))) 86 | ) 87 | .returns(Maybe[T.all(BasicObject, T.type_parameter(:Default))]) 88 | end 89 | def map(&_block) 90 | mapped = yield value 91 | Present[T.all(BasicObject, T.type_parameter(:Default))].new(mapped) 92 | end 93 | 94 | sig(:final) { override.params(other: BasicObject).returns(T::Boolean) } 95 | def ==(other) 96 | return false unless self.class === other # rubocop:disable Style/CaseEquality 97 | 98 | value == other.value 99 | end 100 | 101 | sig(:final) { returns(Value) } 102 | attr_reader :value 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /lib/schema_registry/property.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: strict 3 | 4 | require "date" 5 | require "bigdecimal" 6 | require_relative "property_type" 7 | require_relative "maybe" 8 | require_relative "../resource_registry/versions/version" 9 | 10 | module SchemaRegistry 11 | class Property < T::Struct 12 | extend T::Sig 13 | 14 | ValueType = 15 | T.type_alias do 16 | T.nilable( 17 | T.any( 18 | T::Boolean, 19 | Integer, 20 | Float, 21 | BigDecimal, 22 | String, 23 | Date, 24 | Time, 25 | DateTime, 26 | T::Array[T.untyped], 27 | T::Hash[String, T.untyped], 28 | Maybe[T.untyped], 29 | ResourceRegistry::Versions::Version 30 | ) 31 | ) 32 | end 33 | 34 | const :name, String 35 | const :types, T::Array[PropertyType] 36 | const :type_name, T.nilable(String) 37 | const :items, T::Array[Property], default: [] 38 | const :properties, T::Array[Property], default: [] 39 | const :description, T.nilable(String), default: nil 40 | const :resolver, T.nilable(T::Hash[Symbol, String]), default: nil 41 | const :resolvable, T::Boolean, default: false 42 | const :deprecated, T::Boolean, default: false 43 | const :deprecated_on, T.nilable(Date), default: nil 44 | const :example, T.nilable(ValueType), default: nil 45 | const :enum_values, T.nilable(T::Array[String]), default: [] 46 | const :required, T::Boolean 47 | const :default, ValueType, default: nil 48 | const :serialization_groups, T::Set[Symbol], default: Set[] 49 | 50 | sig { returns(T::Boolean) } 51 | def deprecated? 52 | return false if deprecated_on.blank? 53 | 54 | T.must(deprecated_on) < Time.zone.today 55 | end 56 | 57 | sig { returns(T::Boolean) } 58 | def null? 59 | @types == [PropertyType::Null] 60 | end 61 | 62 | sig { returns(T::Array[PropertyType]) } 63 | def types 64 | return @types if null? 65 | 66 | @types - [PropertyType::Null] 67 | end 68 | 69 | sig { returns(T::Boolean) } 70 | def nilable? 71 | return false if null? 72 | 73 | @types.include?(PropertyType::Null) 74 | end 75 | 76 | sig { returns(T::Boolean) } 77 | def required? 78 | required 79 | end 80 | 81 | sig { returns(T::Boolean) } 82 | def enum? 83 | enum_values.present? 84 | end 85 | 86 | sig { returns(T::Boolean) } 87 | def value_object? 88 | !!type_name.to_s.split("::").any?("ValueObjects") 89 | end 90 | 91 | sig { params(group: Symbol).returns(T::Boolean) } 92 | def serialization_group?(group) 93 | serialization_groups.include?(group) 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/schema_registry/property_mapper.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module SchemaRegistry 4 | class PropertyMapper 5 | extend T::Sig 6 | 7 | # TODO: Proper type json-schema 8 | sig { params(schema: Schema).void } 9 | def initialize(schema:) 10 | @schema = schema 11 | end 12 | 13 | sig { returns(T::Hash[String, T.untyped]) } 14 | def call 15 | schema 16 | .properties 17 | .each_with_object({}) do |property, memo| 18 | json_type = sorbet_to_json_schema(property) 19 | json_type = ["null", json_type] if property.nilable? 20 | 21 | memo[property.name] = { "type" => json_type } 22 | end 23 | end 24 | 25 | private 26 | 27 | sig { returns(Schema) } 28 | attr_reader :schema 29 | 30 | sig { params(property: Property).returns(T.untyped) } 31 | def sorbet_to_json_schema(property) 32 | properties = property.types.map(&:serialize) 33 | 34 | return "integer" if properties.include?("integer") 35 | return "boolean" if properties.include?("boolean") 36 | return "number" if properties.include?("number") 37 | 38 | "string" 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/schema_registry/property_type.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module SchemaRegistry 4 | extend T::Sig 5 | 6 | class PropertyType < T::Enum 7 | extend T::Sig 8 | 9 | enums do 10 | # Basic types 11 | String = new 12 | Number = new 13 | Integer = new 14 | BigInteger = new 15 | Object = new 16 | Array = new 17 | Boolean = new 18 | Null = new 19 | 20 | # String format specializations 21 | DateTime = new 22 | Time = new 23 | Date = new 24 | Duration = new 25 | Email = new 26 | Uri = new 27 | Regex = new 28 | 29 | # Other 30 | File = new 31 | end 32 | 33 | sig { returns(T.nilable(::String)) } 34 | # rubocop:disable Metrics/CyclomaticComplexity 35 | def sorbet_type 36 | case self 37 | when String 38 | "String" 39 | when Number 40 | "Float" 41 | when Integer, BigInteger 42 | "Integer" 43 | when Object 44 | "T::Hash[Symbol, T.untyped]" 45 | when Array 46 | "T::Array[T.untyped]" 47 | when Boolean 48 | "T::Boolean" 49 | when Null 50 | "NilClass" 51 | when DateTime, Time 52 | "Time" 53 | when Date 54 | "Date" 55 | when Duration 56 | "ActiveSupport::Duration" 57 | when Email 58 | "Mail::Address" 59 | when Uri 60 | "URI::Generic" 61 | when Regex 62 | "Regexp" 63 | when File 64 | "ActionDispatch::Http::UploadedFile" 65 | else 66 | T.absurd(self) 67 | end 68 | end 69 | # rubocop:enable Metrics/CyclomaticComplexity 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/schema_registry/registry.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module SchemaRegistry 4 | class Registry 5 | extend T::Sig 6 | 7 | SchemaNotFound = Class.new(StandardError) 8 | 9 | sig { void } 10 | def initialize 11 | @schemas = T.let({}, T::Hash[Symbol, Schema]) 12 | end 13 | 14 | sig { params(schema: Schema).returns(SchemaRegistry::Registry) } 15 | def register(schema) 16 | @schemas[schema.identifier] = schema 17 | 18 | self 19 | end 20 | 21 | sig do 22 | params(identifier: Symbol).returns(T.nilable(SchemaRegistry::Schema)) 23 | end 24 | def fetch!(identifier) 25 | unless @schemas[identifier] 26 | raise SchemaNotFound, "Schema for #{identifier} can not be found" 27 | end 28 | 29 | @schemas[identifier] 30 | end 31 | 32 | sig do 33 | params(identifier: Symbol).returns(T.nilable(SchemaRegistry::Schema)) 34 | end 35 | def fetch(identifier) 36 | @schemas[identifier] 37 | end 38 | 39 | sig { returns(T::Hash[Symbol, SchemaRegistry::Schema]) } 40 | def fetch_all 41 | @schemas 42 | end 43 | 44 | sig { returns(SchemaRegistry::Registry) } 45 | def cleanup 46 | @schemas = {} 47 | 48 | self 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/schema_registry/schema.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: strict 3 | 4 | require_relative "property" 5 | require_relative "filter_field" 6 | require_relative "property_mapper" 7 | 8 | module SchemaRegistry 9 | class Schema < T::Struct 10 | extend T::Sig 11 | 12 | const :name, String 13 | const :namespace, String 14 | const :properties, T::Array[Property] 15 | const :raw_json_schema, T::Hash[String, T.untyped], default: {} 16 | const :additional_filter_fields, T::Array[FilterField], default: [] 17 | 18 | sig { returns(String) } 19 | def schema_module_name 20 | name 21 | end 22 | 23 | sig { returns(String) } 24 | def slug 25 | name.underscore 26 | end 27 | 28 | sig { returns(Symbol) } 29 | def identifier 30 | :"#{namespace.underscore}.#{slug}" 31 | end 32 | 33 | sig { returns(String) } 34 | def namespace_with_slug 35 | "#{namespace.underscore}_#{slug}" 36 | end 37 | 38 | sig { params(name: String).returns(T.nilable(Property)) } 39 | def find_property(name) 40 | properties.find { |property| property.name == name } 41 | end 42 | 43 | sig { params(name: String).returns(T.nilable(FilterField)) } 44 | def find_additional_filter_field(name) 45 | additional_filter_fields.find { |field| field.name == name } 46 | end 47 | 48 | sig { params(name: String).returns(T::Boolean) } 49 | def has_property?(name) 50 | find_property(name).present? 51 | end 52 | 53 | sig { returns(String) } 54 | def namespace_with_slug 55 | "#{namespace.underscore}_#{slug}" 56 | end 57 | 58 | sig { params(name: String).returns(T.nilable(T::Hash[Symbol, String])) } 59 | def get_resolver(name) 60 | property = find_property(name) 61 | unless property&.resolvable 62 | raise "No resolvable schema property of name #{name} exists" 63 | end 64 | 65 | property.resolver 66 | end 67 | 68 | sig { returns(T::Hash[String, T.untyped]) } 69 | def raw_json_schema 70 | return @raw_json_schema if @raw_json_schema.present? 71 | 72 | properties = PropertyMapper.new(schema: self).call 73 | 74 | { name => { "type" => "object", "properties" => properties } } 75 | end 76 | 77 | sig { returns(T::Hash[Symbol, T.untyped]) } 78 | def dump 79 | {}.tap do |result| 80 | result["namespace"] = namespace 81 | result["raw_json_schema"] = raw_json_schema 82 | end 83 | end 84 | 85 | sig { params(spec: T.untyped).returns(Schema) } 86 | def self.load(spec) 87 | JsonSchemaMapper.new( 88 | namespace: spec["namespace"], 89 | definition: spec["raw_json_schema"] 90 | ).call 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /rbi/resource_registry.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | module Maybe 4 | sig do 5 | type_parameters(:Value) 6 | .params(value: T.all(BasicObject, T.type_parameter(:Value))) 7 | .returns(Maybe[T.all(BasicObject, T.type_parameter(:Value))]) 8 | end 9 | # Creates an instance containing the specified value. 10 | # Necessary to make this work with sorbet-coerce 11 | def self.new(value); end 12 | end 13 | -------------------------------------------------------------------------------- /resource_registry.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = "resource_registry" 3 | s.version = "0.0.0" 4 | s.summary = "Resource Registry" 5 | s.description = 6 | "A declarative approach to define resources and their relationships" 7 | s.authors = ["Genar Trias Ortiz"] 8 | s.email = "genar.factorial@factorial.co" 9 | s.files = %w[ 10 | lib/resource_registry.rb 11 | lib/resource_registry/repositories/base.rb 12 | rbi/resource_registry.rbi 13 | ] 14 | s.homepage = "https://rubygems.org/gems/resource_registry" 15 | s.license = "MIT" 16 | s.required_ruby_version = ">= 3.0" 17 | s.add_development_dependency "sorbet", "0.5.11845" 18 | s.add_runtime_dependency "sorbet-runtime", "0.5.11845" 19 | s.add_dependency "sorbet-coerce", "~> 0.7" 20 | s.add_runtime_dependency "activesupport", ">= 7.1.3" 21 | end 22 | -------------------------------------------------------------------------------- /sorbet/config: -------------------------------------------------------------------------------- 1 | --dir 2 | . 3 | --ignore=/tmp/ 4 | --ignore=/vendor/bundle 5 | --ignore=/examples -------------------------------------------------------------------------------- /sorbet/rbi/annotations/.gitattributes: -------------------------------------------------------------------------------- 1 | **/*.rbi linguist-vendored=true 2 | -------------------------------------------------------------------------------- /sorbet/rbi/annotations/minitest.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This file was pulled from a central RBI files repository. 5 | # Please run `bin/tapioca annotations` to update it. 6 | 7 | module Minitest::Assertions 8 | sig { params(test: T.anything, msg: T.anything).returns(TrueClass) } 9 | def assert(test, msg = nil); end 10 | 11 | sig { params(obj: T.anything, msg: T.anything).returns(TrueClass) } 12 | def assert_empty(obj, msg = nil); end 13 | 14 | sig { params(exp: T.anything, act: T.anything, msg: T.anything).returns(TrueClass) } 15 | def assert_equal(exp, act, msg = nil); end 16 | 17 | sig { params(exp: T.anything, act: T.anything, delta: Numeric, msg: T.anything).returns(TrueClass) } 18 | def assert_in_delta(exp, act, delta = T.unsafe(nil), msg = nil); end 19 | 20 | sig { params(a: T.anything, b: T.anything, epsilon: Numeric, msg: T.anything).returns(TrueClass) } 21 | def assert_in_epsilon(a, b, epsilon = T.unsafe(nil), msg = nil); end 22 | 23 | sig { params(collection: T.anything, obj: T.anything, msg: T.anything).returns(TrueClass) } 24 | def assert_includes(collection, obj, msg = nil); end 25 | 26 | sig { params(cls: T.anything, obj: T.anything, msg: T.anything).returns(TrueClass) } 27 | def assert_instance_of(cls, obj, msg = nil); end 28 | 29 | sig { params(cls: T.anything, obj: T.anything, msg: T.anything).returns(TrueClass) } 30 | def assert_kind_of(cls, obj, msg = nil); end 31 | 32 | sig { params(matcher: T.any(String, Regexp), obj: T.anything, msg: T.anything).returns(MatchData) } 33 | def assert_match(matcher, obj, msg = nil); end 34 | 35 | sig { params(obj: T.anything, msg: T.anything).returns(TrueClass) } 36 | def assert_nil(obj, msg = nil); end 37 | 38 | sig { params(o1: T.anything, op: T.any(Symbol, String), o2: T.anything, msg: T.anything).returns(TrueClass) } 39 | def assert_operator(o1, op, o2 = T.unsafe(nil), msg = nil); end 40 | 41 | sig { params(stdout: T.nilable(T.any(String, Regexp)), stderr: T.nilable(T.any(String, Regexp)), block: T.proc.void).returns(T::Boolean) } 42 | def assert_output(stdout = nil, stderr = nil, &block); end 43 | 44 | sig { params(path: T.any(String, Pathname), msg: T.anything).returns(TrueClass) } 45 | def assert_path_exists(path, msg = nil); end 46 | 47 | sig { params(block: T.proc.void).returns(TrueClass) } 48 | def assert_pattern(&block); end 49 | 50 | sig { params(o1: T.anything, op: T.any(String, Symbol), msg: T.anything).returns(TrueClass) } 51 | def assert_predicate(o1, op, msg = nil); end 52 | 53 | sig { params(exp: NilClass, block: T.proc.void).returns(StandardError) } 54 | sig { type_parameters(:T).params(exp: T.any(T::Class[T.type_parameter(:T)], Regexp, String), block: T.proc.void).returns(T.type_parameter(:T)) } 55 | def assert_raises(*exp, &block); end 56 | 57 | sig { params(obj: T.anything, meth: T.any(String, Symbol), msg: T.anything, include_all: T::Boolean).returns(TrueClass) } 58 | def assert_respond_to(obj, meth, msg = nil, include_all: false); end 59 | 60 | sig { params(exp: T.anything, act: T.anything, msg: T.anything).returns(TrueClass) } 61 | def assert_same(exp, act, msg = nil); end 62 | 63 | sig { params(send_ary: T::Array[T.anything], m: T.anything).returns(T::Boolean) } 64 | def assert_send(send_ary, m = nil); end 65 | 66 | sig { params(block: T.proc.void).returns(T::Boolean) } 67 | def assert_silent(&block); end 68 | 69 | sig { params(sym: Symbol, msg: T.anything, block: T.proc.void).returns(T.anything) } 70 | def assert_throws(sym, msg = nil, &block); end 71 | 72 | sig { params(test: T.anything, msg: T.anything).returns(TrueClass) } 73 | def refute(test, msg = nil); end 74 | 75 | sig { params(obj: T.anything, msg: T.anything).returns(TrueClass) } 76 | def refute_empty(obj, msg = nil); end 77 | 78 | sig { params(exp: T.anything, act: T.anything, msg: T.anything).returns(TrueClass) } 79 | def refute_equal(exp, act, msg = nil); end 80 | 81 | sig { params(exp: T.anything, act: T.anything, delta: Numeric, msg: T.anything).returns(TrueClass) } 82 | def refute_in_delta(exp, act, delta = T.unsafe(nil), msg = nil); end 83 | 84 | sig { params(a: T.anything, b: T.anything, epsilon: Numeric, msg: T.anything).returns(TrueClass) } 85 | def refute_in_epsilon(a, b, epsilon = T.unsafe(nil), msg = nil); end 86 | 87 | sig { params(collection: T.anything, obj: T.anything, msg: T.anything).returns(TrueClass) } 88 | def refute_includes(collection, obj, msg = nil); end 89 | 90 | sig { params(cls: T.anything, obj: T.anything, msg: T.anything).returns(TrueClass) } 91 | def refute_instance_of(cls, obj, msg = nil); end 92 | 93 | sig { params(cls: T.anything, obj: T.anything, msg: T.anything).returns(TrueClass) } 94 | def refute_kind_of(cls, obj, msg = nil); end 95 | 96 | sig { params(matcher: T.any(String, Regexp), obj: T.anything, msg: T.anything).returns(TrueClass) } 97 | def refute_match(matcher, obj, msg = nil); end 98 | 99 | sig { params(obj: T.anything, msg: T.anything).returns(TrueClass) } 100 | def refute_nil(obj, msg = nil); end 101 | 102 | sig { params(block: T.proc.void).returns(TrueClass) } 103 | def refute_pattern(&block); end 104 | 105 | sig { params(o1: T.anything, op: T.any(Symbol, String), o2: T.anything, msg: T.anything).returns(TrueClass) } 106 | def refute_operator(o1, op, o2 = T.unsafe(nil), msg = nil); end 107 | 108 | sig { params(path: T.any(String, Pathname), msg: T.anything).returns(TrueClass) } 109 | def refute_path_exists(path, msg = nil); end 110 | 111 | sig { params(o1: T.anything, op: T.any(String, Symbol), msg: T.anything).returns(TrueClass) } 112 | def refute_predicate(o1, op, msg = nil); end 113 | 114 | sig { params(obj: T.anything, meth: T.any(String, Symbol), msg: T.anything, include_all: T::Boolean).returns(TrueClass) } 115 | def refute_respond_to(obj, meth, msg = nil, include_all: false); end 116 | 117 | sig { params(exp: T.anything, act: T.anything, msg: T.anything).returns(TrueClass) } 118 | def refute_same(exp, act, msg = nil); end 119 | end 120 | -------------------------------------------------------------------------------- /sorbet/rbi/dsl/.gitattributes: -------------------------------------------------------------------------------- 1 | **/*.rbi linguist-generated=true 2 | -------------------------------------------------------------------------------- /sorbet/rbi/dsl/active_support/callbacks.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for dynamic methods in `ActiveSupport::Callbacks`. 5 | # Please instead update this file by running `bin/tapioca dsl ActiveSupport::Callbacks`. 6 | 7 | 8 | module ActiveSupport::Callbacks 9 | include GeneratedInstanceMethods 10 | 11 | mixes_in_class_methods GeneratedClassMethods 12 | 13 | module GeneratedClassMethods 14 | def __callbacks; end 15 | def __callbacks=(value); end 16 | def __callbacks?; end 17 | end 18 | 19 | module GeneratedInstanceMethods 20 | def __callbacks; end 21 | def __callbacks?; end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/.gitattributes: -------------------------------------------------------------------------------- 1 | **/*.rbi linguist-generated=true 2 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/base64@0.2.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `base64` gem. 5 | # Please instead update this file by running `bin/tapioca gem base64`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/bigdecimal.rbi: -------------------------------------------------------------------------------- 1 | # This file is autogenerated. Do not edit it by hand. Regenerate it with: 2 | # srb rbi gems 3 | 4 | # typed: false 5 | # 6 | # If you would like to make changes to this file, great! Please create the gem's shim here: 7 | # 8 | # https://github.com/sorbet/sorbet-typed/new/master?filename=lib/bigdecimal/all/bigdecimal.rbi 9 | # 10 | # bigdecimal-3.1.8 11 | 12 | module Kernel 13 | def BigDecimal(*arg0); end 14 | def self.BigDecimal(*arg0); end 15 | end 16 | class BigDecimal < Numeric 17 | def %(arg0); end 18 | def *(arg0); end 19 | def **(arg0); end 20 | def +(arg0); end 21 | def +@; end 22 | def -(arg0); end 23 | def -@; end 24 | def /(arg0); end 25 | def <(arg0); end 26 | def <=(arg0); end 27 | def <=>(arg0); end 28 | def ==(arg0); end 29 | def ===(arg0); end 30 | def >(arg0); end 31 | def >=(arg0); end 32 | def _dump(*arg0); end 33 | def abs; end 34 | def add(arg0, arg1); end 35 | def ceil(*arg0); end 36 | def clone; end 37 | def coerce(arg0); end 38 | def div(*arg0); end 39 | def divmod(arg0); end 40 | def dup; end 41 | def eql?(arg0); end 42 | def exponent; end 43 | def finite?; end 44 | def fix; end 45 | def floor(*arg0); end 46 | def frac; end 47 | def hash; end 48 | def infinite?; end 49 | def inspect; end 50 | def modulo(arg0); end 51 | def mult(arg0, arg1); end 52 | def n_significant_digits; end 53 | def nan?; end 54 | def nonzero?; end 55 | def power(*arg0); end 56 | def precision; end 57 | def precision_scale; end 58 | def precs; end 59 | def quo(*arg0); end 60 | def remainder(arg0); end 61 | def round(*arg0); end 62 | def scale; end 63 | def self._load(arg0); end 64 | def self.double_fig; end 65 | def self.interpret_loosely(arg0); end 66 | def self.limit(*arg0); end 67 | def self.mode(*arg0); end 68 | def self.save_exception_mode; end 69 | def self.save_limit; end 70 | def self.save_rounding_mode; end 71 | def sign; end 72 | def split; end 73 | def sqrt(arg0); end 74 | def sub(arg0, arg1); end 75 | def to_f; end 76 | def to_i; end 77 | def to_int; end 78 | def to_r; end 79 | def to_s(*arg0); end 80 | def truncate(*arg0); end 81 | def zero?; end 82 | end 83 | module BigMath 84 | def self.exp(arg0, arg1); end 85 | def self.log(arg0, arg1); end 86 | end 87 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/bigdecimal@3.1.8.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `bigdecimal` gem. 5 | # Please instead update this file by running `bin/tapioca gem bigdecimal`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/concurrent-ruby@1.3.4.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `concurrent-ruby` gem. 5 | # Please instead update this file by running `bin/tapioca gem concurrent-ruby`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/connection_pool.rbi: -------------------------------------------------------------------------------- 1 | # This file is autogenerated. Do not edit it by hand. Regenerate it with: 2 | # srb rbi gems 3 | 4 | # typed: strict 5 | # 6 | # If you would like to make changes to this file, great! Please create the gem's shim here: 7 | # 8 | # https://github.com/sorbet/sorbet-typed/new/master?filename=lib/connection_pool/all/connection_pool.rbi 9 | # 10 | # connection_pool-2.4.1 11 | 12 | class ConnectionPool 13 | def auto_reload_after_fork; end 14 | def available; end 15 | def checkin(force: nil); end 16 | def checkout(options = nil); end 17 | def initialize(options = nil, &block); end 18 | def reload(&block); end 19 | def self.after_fork; end 20 | def self.wrap(options, &block); end 21 | def shutdown(&block); end 22 | def size; end 23 | def then(options = nil); end 24 | def with(options = nil); end 25 | end 26 | class ConnectionPool::TimedStack 27 | def <<(obj, options = nil); end 28 | def connection_stored?(options = nil); end 29 | def current_time; end 30 | def empty?; end 31 | def fetch_connection(options = nil); end 32 | def initialize(size = nil, &block); end 33 | def length; end 34 | def max; end 35 | def pop(timeout = nil, options = nil); end 36 | def push(obj, options = nil); end 37 | def shutdown(reload: nil, &block); end 38 | def shutdown_connections(options = nil); end 39 | def store_connection(obj, options = nil); end 40 | def try_create(options = nil); end 41 | end 42 | class ConnectionPool::Wrapper < BasicObject 43 | def initialize(options = nil, &block); end 44 | def method_missing(name, *args, **kwargs, &block); end 45 | def pool_available; end 46 | def pool_shutdown(&block); end 47 | def pool_size; end 48 | def respond_to?(id, *args); end 49 | def with(&block); end 50 | def wrapped_pool; end 51 | end 52 | class ConnectionPool::Error < RuntimeError 53 | end 54 | class ConnectionPool::PoolShuttingDownError < ConnectionPool::Error 55 | end 56 | class ConnectionPool::TimeoutError < Timeout::Error 57 | end 58 | module ConnectionPool::ForkTracker 59 | def _fork; end 60 | end 61 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/connection_pool@2.4.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `connection_pool` gem. 5 | # Please instead update this file by running `bin/tapioca gem connection_pool`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/drb.rbi: -------------------------------------------------------------------------------- 1 | # This file is autogenerated. Do not edit it by hand. Regenerate it with: 2 | # srb rbi gems 3 | 4 | # typed: false 5 | # 6 | # If you would like to make changes to this file, great! Please create the gem's shim here: 7 | # 8 | # https://github.com/sorbet/sorbet-typed/new/master?filename=lib/drb/all/drb.rbi 9 | # 10 | # drb-2.2.1 11 | 12 | module DRb 13 | def config; end 14 | def current_server; end 15 | def fetch_server(uri); end 16 | def front; end 17 | def here?(uri); end 18 | def install_acl(acl); end 19 | def install_id_conv(idconv); end 20 | def mutex; end 21 | def primary_server; end 22 | def primary_server=(arg0); end 23 | def regist_server(server); end 24 | def remove_server(server); end 25 | def self.config; end 26 | def self.current_server; end 27 | def self.fetch_server(uri); end 28 | def self.front; end 29 | def self.here?(uri); end 30 | def self.install_acl(acl); end 31 | def self.install_id_conv(idconv); end 32 | def self.mutex; end 33 | def self.primary_server; end 34 | def self.primary_server=(arg0); end 35 | def self.regist_server(server); end 36 | def self.remove_server(server); end 37 | def self.start_service(uri = nil, front = nil, config = nil); end 38 | def self.stop_service; end 39 | def self.thread; end 40 | def self.to_id(obj); end 41 | def self.to_obj(ref); end 42 | def self.uri; end 43 | def start_service(uri = nil, front = nil, config = nil); end 44 | def stop_service; end 45 | def thread; end 46 | def to_id(obj); end 47 | def to_obj(ref); end 48 | def uri; end 49 | end 50 | class DRb::DRbObject 51 | def ==(other); end 52 | def __drbref; end 53 | def __drburi; end 54 | def _dump(lv); end 55 | def eql?(other); end 56 | def hash; end 57 | def initialize(obj, uri = nil); end 58 | def method_missing(msg_id, *a, **, &b); end 59 | def pretty_print(q); end 60 | def pretty_print_cycle(q); end 61 | def respond_to?(msg_id, priv = nil); end 62 | def self._load(s); end 63 | def self.new_with(uri, ref); end 64 | def self.new_with_uri(uri); end 65 | def self.prepare_backtrace(uri, result); end 66 | def self.with_friend(uri); end 67 | end 68 | class DRb::DRbServer 69 | def alive?; end 70 | def any_to_s(obj); end 71 | def check_insecure_method(obj, msg_id); end 72 | def config; end 73 | def error_print(exception); end 74 | def front; end 75 | def here?(uri); end 76 | def initialize(uri = nil, front = nil, config_or_acl = nil); end 77 | def insecure_method?(msg_id); end 78 | def main_loop; end 79 | def run; end 80 | def self.default_acl(acl); end 81 | def self.default_argc_limit(argc); end 82 | def self.default_id_conv(idconv); end 83 | def self.default_load_limit(sz); end 84 | def self.make_config(hash = nil); end 85 | def self.verbose; end 86 | def self.verbose=(on); end 87 | def shutdown; end 88 | def stop_service; end 89 | def thread; end 90 | def to_id(obj); end 91 | def to_obj(ref); end 92 | def uri; end 93 | def verbose; end 94 | def verbose=(v); end 95 | end 96 | module DRb::DRbServer::InvokeMethod18Mixin 97 | def block_yield(x); end 98 | def perform_with_block; end 99 | end 100 | class DRb::DRbError < RuntimeError 101 | end 102 | class DRb::DRbConnError < DRb::DRbError 103 | end 104 | class DRb::DRbIdConv 105 | def to_id(obj); end 106 | def to_obj(ref); end 107 | end 108 | module DRb::DRbUndumped 109 | def _dump(dummy); end 110 | end 111 | class DRb::DRbServerNotFound < DRb::DRbError 112 | end 113 | class DRb::DRbBadURI < DRb::DRbError 114 | end 115 | class DRb::DRbBadScheme < DRb::DRbError 116 | end 117 | class DRb::DRbUnknownError < DRb::DRbError 118 | def _dump(lv); end 119 | def initialize(unknown); end 120 | def self._load(s); end 121 | def unknown; end 122 | end 123 | class DRb::DRbRemoteError < DRb::DRbError 124 | def initialize(error); end 125 | def reason; end 126 | end 127 | class DRb::DRbUnknown 128 | def _dump(lv); end 129 | def buf; end 130 | def exception; end 131 | def initialize(err, buf); end 132 | def name; end 133 | def reload; end 134 | def self._load(s); end 135 | end 136 | class DRb::DRbArray 137 | def _dump(lv); end 138 | def initialize(ary); end 139 | def self._load(s); end 140 | end 141 | class DRb::DRbMessage 142 | def dump(obj, error = nil); end 143 | def initialize(config); end 144 | def load(soc); end 145 | def make_proxy(obj, error = nil); end 146 | def recv_reply(stream); end 147 | def recv_request(stream); end 148 | def send_reply(stream, succ, result); end 149 | def send_request(stream, ref, msg_id, arg, b); end 150 | end 151 | module DRb::DRbProtocol 152 | def add_protocol(prot); end 153 | def auto_load(uri); end 154 | def open(uri, config, first = nil); end 155 | def open_server(uri, config, first = nil); end 156 | def self.add_protocol(prot); end 157 | def self.auto_load(uri); end 158 | def self.open(uri, config, first = nil); end 159 | def self.open_server(uri, config, first = nil); end 160 | def self.uri_option(uri, config, first = nil); end 161 | def uri_option(uri, config, first = nil); end 162 | end 163 | class DRb::DRbTCPSocket 164 | def accept; end 165 | def accept_or_shutdown; end 166 | def alive?; end 167 | def close; end 168 | def close_shutdown_pipe; end 169 | def initialize(uri, soc, config = nil); end 170 | def peeraddr; end 171 | def recv_reply; end 172 | def recv_request; end 173 | def self.getservername; end 174 | def self.open(uri, config); end 175 | def self.open_server(uri, config); end 176 | def self.open_server_inaddr_any(host, port); end 177 | def self.parse_uri(uri); end 178 | def self.uri_option(uri, config); end 179 | def send_reply(succ, result); end 180 | def send_request(ref, msg_id, arg, b); end 181 | def set_sockopt(soc); end 182 | def shutdown; end 183 | def stream; end 184 | def uri; end 185 | end 186 | class DRb::DRbURIOption 187 | def ==(other); end 188 | def eql?(other); end 189 | def hash; end 190 | def initialize(option); end 191 | def option; end 192 | def to_s; end 193 | end 194 | class DRb::ThreadObject 195 | def _execute; end 196 | def alive?; end 197 | def initialize(&blk); end 198 | def kill; end 199 | def method_missing(msg, *arg, &blk); end 200 | include MonitorMixin 201 | end 202 | class DRb::DRbConn 203 | def alive?; end 204 | def close; end 205 | def initialize(remote_uri); end 206 | def self.make_pool; end 207 | def self.open(remote_uri); end 208 | def self.stop_pool; end 209 | def send_message(ref, msg_id, arg, block); end 210 | def uri; end 211 | end 212 | class DRb::DRbServer::InvokeMethod 213 | def check_insecure_method; end 214 | def init_with_client; end 215 | def initialize(drb_server, client); end 216 | def perform; end 217 | def perform_without_block; end 218 | def setup_message; end 219 | include DRb::DRbServer::InvokeMethod18Mixin 220 | end 221 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/erubi@1.13.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `erubi` gem. 5 | # Please instead update this file by running `bin/tapioca gem erubi`. 6 | 7 | 8 | # source://erubi//lib/erubi.rb#3 9 | module Erubi 10 | private 11 | 12 | def h(_arg0); end 13 | 14 | class << self 15 | def h(_arg0); end 16 | end 17 | end 18 | 19 | # source://erubi//lib/erubi.rb#51 20 | class Erubi::Engine 21 | # Initialize a new Erubi::Engine. Options: 22 | # +:bufval+ :: The value to use for the buffer variable, as a string (default '::String.new'). 23 | # +:bufvar+ :: The variable name to use for the buffer variable, as a string. 24 | # +:chain_appends+ :: Whether to chain << calls to the buffer variable. Offers better 25 | # performance, but can cause issues when the buffer variable is reassigned during 26 | # template rendering (default +false+). 27 | # +:ensure+ :: Wrap the template in a begin/ensure block restoring the previous value of bufvar. 28 | # +:escapefunc+ :: The function to use for escaping, as a string (default: '::Erubi.h'). 29 | # +:escape+ :: Whether to make <%= escape by default, and <%== not escape by default. 30 | # +:escape_html+ :: Same as +:escape+, with lower priority. 31 | # +:filename+ :: The filename for the template. 32 | # the resulting source code. Note this may cause problems if you are wrapping the resulting 33 | # source code in other code, because the magic comment only has an effect at the beginning of 34 | # the file, and having the magic comment later in the file can trigger warnings. 35 | # +:freeze_template_literals+ :: Whether to suffix all literal strings for template code with .freeze 36 | # (default: +true+ on Ruby 2.1+, +false+ on Ruby 2.0 and older). 37 | # Can be set to +false+ on Ruby 2.3+ when frozen string literals are enabled 38 | # in order to improve performance. 39 | # +:literal_prefix+ :: The prefix to output when using escaped tag delimiters (default '<%'). 40 | # +:literal_postfix+ :: The postfix to output when using escaped tag delimiters (default '%>'). 41 | # +:outvar+ :: Same as +:bufvar+, with lower priority. 42 | # +:postamble+ :: The postamble for the template, by default returns the resulting source code. 43 | # +:preamble+ :: The preamble for the template, by default initializes the buffer variable. 44 | # +:regexp+ :: The regexp to use for scanning. 45 | # +:src+ :: The initial value to use for the source code, an empty string by default. 46 | # +:trim+ :: Whether to trim leading and trailing whitespace, true by default. 47 | # 48 | # @return [Engine] a new instance of Engine 49 | # 50 | # source://erubi//lib/erubi.rb#91 51 | def initialize(input, properties = T.unsafe(nil)); end 52 | 53 | # The variable name used for the buffer variable. 54 | # 55 | # source://erubi//lib/erubi.rb#62 56 | def bufvar; end 57 | 58 | # The filename of the template, if one was given. 59 | # 60 | # source://erubi//lib/erubi.rb#59 61 | def filename; end 62 | 63 | # The frozen ruby source code generated from the template, which can be evaled. 64 | # 65 | # source://erubi//lib/erubi.rb#56 66 | def src; end 67 | 68 | private 69 | 70 | # :nocov: 71 | # 72 | # source://erubi//lib/erubi.rb#209 73 | def _dup_string_if_frozen(string); end 74 | 75 | # Add ruby code to the template 76 | # 77 | # source://erubi//lib/erubi.rb#232 78 | def add_code(code); end 79 | 80 | # Add the given ruby expression result to the template, 81 | # escaping it based on the indicator given and escape flag. 82 | # 83 | # source://erubi//lib/erubi.rb#241 84 | def add_expression(indicator, code); end 85 | 86 | # Add the result of Ruby expression to the template 87 | # 88 | # source://erubi//lib/erubi.rb#250 89 | def add_expression_result(code); end 90 | 91 | # Add the escaped result of Ruby expression to the template 92 | # 93 | # source://erubi//lib/erubi.rb#255 94 | def add_expression_result_escaped(code); end 95 | 96 | # Add the given postamble to the src. Can be overridden in subclasses 97 | # to make additional changes to src that depend on the current state. 98 | # 99 | # source://erubi//lib/erubi.rb#261 100 | def add_postamble(postamble); end 101 | 102 | # Add raw text to the template. Modifies argument if argument is mutable as a memory optimization. 103 | # Must be called with a string, cannot be called with nil (Rails's subclass depends on it). 104 | # 105 | # source://erubi//lib/erubi.rb#222 106 | def add_text(text); end 107 | 108 | # Raise an exception, as the base engine class does not support handling other indicators. 109 | # 110 | # @raise [ArgumentError] 111 | # 112 | # source://erubi//lib/erubi.rb#267 113 | def handle(indicator, code, tailch, rspace, lspace); end 114 | 115 | # Make sure that any current expression has been terminated. 116 | # The default is to terminate all expressions, but when 117 | # the chain_appends option is used, expressions may not be 118 | # terminated. 119 | # 120 | # source://erubi//lib/erubi.rb#295 121 | def terminate_expression; end 122 | 123 | # Make sure the buffer variable is the target of the next append 124 | # before yielding to the block. Mark that the buffer is the target 125 | # of the next append after the block executes. 126 | # 127 | # This method should only be called if the block will result in 128 | # code where << will append to the bufvar. 129 | # 130 | # source://erubi//lib/erubi.rb#277 131 | def with_buffer; end 132 | end 133 | 134 | # The default regular expression used for scanning. 135 | # 136 | # source://erubi//lib/erubi.rb#53 137 | Erubi::Engine::DEFAULT_REGEXP = T.let(T.unsafe(nil), Regexp) 138 | 139 | # source://erubi//lib/erubi.rb#17 140 | Erubi::FREEZE_TEMPLATE_LITERALS = T.let(T.unsafe(nil), TrueClass) 141 | 142 | # source://erubi//lib/erubi.rb#15 143 | Erubi::MATCH_METHOD = T.let(T.unsafe(nil), Symbol) 144 | 145 | # source://erubi//lib/erubi.rb#8 146 | Erubi::RANGE_FIRST = T.let(T.unsafe(nil), Integer) 147 | 148 | # source://erubi//lib/erubi.rb#9 149 | Erubi::RANGE_LAST = T.let(T.unsafe(nil), Integer) 150 | 151 | # source://erubi//lib/erubi.rb#16 152 | Erubi::SKIP_DEFINED_FOR_INSTANCE_VARIABLE = T.let(T.unsafe(nil), TrueClass) 153 | 154 | # source://erubi//lib/erubi.rb#4 155 | Erubi::VERSION = T.let(T.unsafe(nil), String) 156 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/ffi@1.17.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `ffi` gem. 5 | # Please instead update this file by running `bin/tapioca gem ffi`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/formatador@1.1.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `formatador` gem. 5 | # Please instead update this file by running `bin/tapioca gem formatador`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/guard-compat@1.2.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `guard-compat` gem. 5 | # Please instead update this file by running `bin/tapioca gem guard-compat`. 6 | 7 | 8 | # Provided empty definition so requiring the plugin without Guard won't crash 9 | # (e.g. when added to a Gemfile without `require: false`) 10 | # 11 | # source://guard-compat//lib/guard/compat/plugin.rb#23 12 | module Guard 13 | extend ::Guard::Internals::Helpers 14 | 15 | class << self 16 | # source://guard/2.18.1/lib/guard.rb#87 17 | def async_queue_add(changes); end 18 | 19 | # source://guard/2.18.1/lib/guard.rb#73 20 | def init(cmdline_options); end 21 | 22 | # source://guard/2.18.1/lib/guard.rb#24 23 | def interactor; end 24 | 25 | # source://guard/2.18.1/lib/guard.rb#23 26 | def listener; end 27 | 28 | # source://guard/2.18.1/lib/guard.rb#22 29 | def queue; end 30 | 31 | # source://guard/2.18.1/lib/guard.rb#44 32 | def setup(cmdline_options = T.unsafe(nil)); end 33 | 34 | # source://guard/2.18.1/lib/guard.rb#21 35 | def state; end 36 | 37 | private 38 | 39 | # source://guard/2.18.1/lib/guard.rb#132 40 | def _evaluate(options); end 41 | 42 | # source://guard/2.18.1/lib/guard.rb#152 43 | def _guardfile_deprecated_check(modified); end 44 | 45 | # source://guard/2.18.1/lib/guard.rb#113 46 | def _listener_callback; end 47 | 48 | # source://guard/2.18.1/lib/guard.rb#128 49 | def _pluginless_guardfile?; end 50 | 51 | # source://guard/2.18.1/lib/guard.rb#109 52 | def _relative_pathnames(paths); end 53 | 54 | # source://guard/2.18.1/lib/guard.rb#99 55 | def _relevant_changes?(changes); end 56 | end 57 | end 58 | 59 | # source://guard-compat//lib/guard/compat/plugin.rb#24 60 | module Guard::Compat 61 | class << self 62 | # TODO: this is just a temporary workaround to allow plugins 63 | # to use watcher patterns in run_all 64 | # 65 | # source://guard-compat//lib/guard/compat/plugin.rb#27 66 | def matching_files(plugin, files); end 67 | 68 | # source://guard-compat//lib/guard/compat/plugin.rb#39 69 | def watched_directories; end 70 | end 71 | end 72 | 73 | # source://guard-compat//lib/guard/compat/plugin.rb#54 74 | module Guard::Compat::UI 75 | class << self 76 | # source://guard-compat//lib/guard/compat/plugin.rb#55 77 | def color(text, *colors); end 78 | 79 | # @return [Boolean] 80 | # 81 | # source://guard-compat//lib/guard/compat/plugin.rb#63 82 | def color_enabled?; end 83 | 84 | # source://guard-compat//lib/guard/compat/plugin.rb#95 85 | def debug(message, options = T.unsafe(nil)); end 86 | 87 | # source://guard-compat//lib/guard/compat/plugin.rb#103 88 | def deprecation(message, options = T.unsafe(nil)); end 89 | 90 | # source://guard-compat//lib/guard/compat/plugin.rb#87 91 | def error(message, options = T.unsafe(nil)); end 92 | 93 | # source://guard-compat//lib/guard/compat/plugin.rb#71 94 | def info(message, options = T.unsafe(nil)); end 95 | 96 | # source://guard-compat//lib/guard/compat/plugin.rb#111 97 | def notify(msg, options = T.unsafe(nil)); end 98 | 99 | # source://guard-compat//lib/guard/compat/plugin.rb#79 100 | def warning(message, options = T.unsafe(nil)); end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/minitest@5.25.2.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `minitest` gem. 5 | # Please instead update this file by running `bin/tapioca gem minitest`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/nenv@0.3.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `nenv` gem. 5 | # Please instead update this file by running `bin/tapioca gem nenv`. 6 | 7 | 8 | # source://nenv//lib/nenv/version.rb#1 9 | module Nenv 10 | class << self 11 | # source://nenv//lib/nenv.rb#26 12 | def instance; end 13 | 14 | # source://nenv//lib/nenv.rb#18 15 | def method_missing(meth, *args); end 16 | 17 | # source://nenv//lib/nenv.rb#22 18 | def reset; end 19 | 20 | # @return [Boolean] 21 | # 22 | # source://nenv//lib/nenv.rb#14 23 | def respond_to?(meth); end 24 | end 25 | end 26 | 27 | # source://nenv//lib/nenv/autoenvironment.rb#3 28 | class Nenv::AutoEnvironment < ::Nenv::Environment 29 | # source://nenv//lib/nenv/autoenvironment.rb#4 30 | def method_missing(meth, *args); end 31 | end 32 | 33 | # source://nenv//lib/nenv/builder.rb#4 34 | module Nenv::Builder 35 | class << self 36 | # source://nenv//lib/nenv/builder.rb#5 37 | def build(&block); end 38 | end 39 | end 40 | 41 | # source://nenv//lib/nenv/environment/dumper.rb#2 42 | class Nenv::Environment 43 | # @return [Environment] a new instance of Environment 44 | # 45 | # source://nenv//lib/nenv/environment.rb#21 46 | def initialize(namespace = T.unsafe(nil)); end 47 | 48 | # source://nenv//lib/nenv/environment.rb#25 49 | def create_method(meth, &block); end 50 | 51 | private 52 | 53 | # source://nenv//lib/nenv/environment.rb#35 54 | def _namespaced_sanitize(meth); end 55 | 56 | # source://nenv//lib/nenv/environment.rb#31 57 | def _sanitize(meth); end 58 | 59 | class << self 60 | # source://nenv//lib/nenv/environment.rb#44 61 | def _create_env_accessor(klass, meth, &block); end 62 | 63 | # source://nenv//lib/nenv/environment.rb#40 64 | def create_method(meth, &block); end 65 | 66 | private 67 | 68 | # source://nenv//lib/nenv/environment.rb#66 69 | def _create_env_reader(klass, meth, &block); end 70 | 71 | # source://nenv//lib/nenv/environment.rb#56 72 | def _create_env_writer(klass, meth, &block); end 73 | 74 | # source://nenv//lib/nenv/environment.rb#76 75 | def _fail_if_accessor_exists(klass, meth); end 76 | end 77 | end 78 | 79 | # source://nenv//lib/nenv/environment.rb#15 80 | class Nenv::Environment::AlreadyExistsError < ::Nenv::Environment::MethodError 81 | # source://nenv//lib/nenv/environment.rb#16 82 | def message; end 83 | end 84 | 85 | # source://nenv//lib/nenv/environment/dumper.rb#3 86 | module Nenv::Environment::Dumper 87 | class << self 88 | # source://nenv//lib/nenv/environment/dumper.rb#6 89 | def setup(&callback); end 90 | end 91 | end 92 | 93 | # source://nenv//lib/nenv/environment/dumper/default.rb#3 94 | module Nenv::Environment::Dumper::Default 95 | class << self 96 | # source://nenv//lib/nenv/environment/dumper/default.rb#4 97 | def call(raw_value); end 98 | end 99 | end 100 | 101 | # source://nenv//lib/nenv/environment.rb#6 102 | class Nenv::Environment::Error < ::ArgumentError; end 103 | 104 | # source://nenv//lib/nenv/environment/loader.rb#3 105 | module Nenv::Environment::Loader 106 | class << self 107 | # source://nenv//lib/nenv/environment/loader.rb#7 108 | def setup(meth, &callback); end 109 | end 110 | end 111 | 112 | # source://nenv//lib/nenv/environment/loader/default.rb#3 113 | module Nenv::Environment::Loader::Default 114 | class << self 115 | # source://nenv//lib/nenv/environment/loader/default.rb#4 116 | def call(raw_value); end 117 | end 118 | end 119 | 120 | # source://nenv//lib/nenv/environment/loader/predicate.rb#3 121 | module Nenv::Environment::Loader::Predicate 122 | class << self 123 | # source://nenv//lib/nenv/environment/loader/predicate.rb#4 124 | def call(raw_value); end 125 | end 126 | end 127 | 128 | # source://nenv//lib/nenv/environment.rb#9 129 | class Nenv::Environment::MethodError < ::Nenv::Environment::Error 130 | # @return [MethodError] a new instance of MethodError 131 | # 132 | # source://nenv//lib/nenv/environment.rb#10 133 | def initialize(meth); end 134 | end 135 | 136 | # source://nenv//lib/nenv/version.rb#2 137 | Nenv::VERSION = T.let(T.unsafe(nil), String) 138 | 139 | class Object < ::BasicObject 140 | include ::Kernel 141 | include ::PP::ObjectMixin 142 | 143 | private 144 | 145 | # source://nenv//lib/nenv.rb#6 146 | def Nenv(namespace = T.unsafe(nil)); end 147 | end 148 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/netrc@0.11.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `netrc` gem. 5 | # Please instead update this file by running `bin/tapioca gem netrc`. 6 | 7 | 8 | # source://netrc//lib/netrc.rb#3 9 | class Netrc 10 | # @return [Netrc] a new instance of Netrc 11 | # 12 | # source://netrc//lib/netrc.rb#166 13 | def initialize(path, data); end 14 | 15 | # source://netrc//lib/netrc.rb#180 16 | def [](k); end 17 | 18 | # source://netrc//lib/netrc.rb#188 19 | def []=(k, info); end 20 | 21 | # source://netrc//lib/netrc.rb#200 22 | def delete(key); end 23 | 24 | # source://netrc//lib/netrc.rb#211 25 | def each(&block); end 26 | 27 | # source://netrc//lib/netrc.rb#196 28 | def length; end 29 | 30 | # source://netrc//lib/netrc.rb#215 31 | def new_item(m, l, p); end 32 | 33 | # Returns the value of attribute new_item_prefix. 34 | # 35 | # source://netrc//lib/netrc.rb#178 36 | def new_item_prefix; end 37 | 38 | # Sets the attribute new_item_prefix 39 | # 40 | # @param value the value to set the attribute new_item_prefix to. 41 | # 42 | # source://netrc//lib/netrc.rb#178 43 | def new_item_prefix=(_arg0); end 44 | 45 | # source://netrc//lib/netrc.rb#219 46 | def save; end 47 | 48 | # source://netrc//lib/netrc.rb#233 49 | def unparse; end 50 | 51 | class << self 52 | # source://netrc//lib/netrc.rb#42 53 | def check_permissions(path); end 54 | 55 | # source://netrc//lib/netrc.rb#33 56 | def config; end 57 | 58 | # @yield [self.config] 59 | # 60 | # source://netrc//lib/netrc.rb#37 61 | def configure; end 62 | 63 | # source://netrc//lib/netrc.rb#10 64 | def default_path; end 65 | 66 | # source://netrc//lib/netrc.rb#14 67 | def home_path; end 68 | 69 | # source://netrc//lib/netrc.rb#85 70 | def lex(lines); end 71 | 72 | # source://netrc//lib/netrc.rb#29 73 | def netrc_filename; end 74 | 75 | # Returns two values, a header and a list of items. 76 | # Each item is a tuple, containing some or all of: 77 | # - machine keyword (including trailing whitespace+comments) 78 | # - machine name 79 | # - login keyword (including surrounding whitespace+comments) 80 | # - login 81 | # - password keyword (including surrounding whitespace+comments) 82 | # - password 83 | # - trailing chars 84 | # This lets us change individual fields, then write out the file 85 | # with all its original formatting. 86 | # 87 | # source://netrc//lib/netrc.rb#129 88 | def parse(ts); end 89 | 90 | # Reads path and parses it as a .netrc file. If path doesn't 91 | # exist, returns an empty object. Decrypt paths ending in .gpg. 92 | # 93 | # source://netrc//lib/netrc.rb#51 94 | def read(path = T.unsafe(nil)); end 95 | 96 | # @return [Boolean] 97 | # 98 | # source://netrc//lib/netrc.rb#112 99 | def skip?(s); end 100 | end 101 | end 102 | 103 | # source://netrc//lib/netrc.rb#8 104 | Netrc::CYGWIN = T.let(T.unsafe(nil), T.untyped) 105 | 106 | # source://netrc//lib/netrc.rb#244 107 | class Netrc::Entry < ::Struct 108 | # Returns the value of attribute login 109 | # 110 | # @return [Object] the current value of login 111 | def login; end 112 | 113 | # Sets the attribute login 114 | # 115 | # @param value [Object] the value to set the attribute login to. 116 | # @return [Object] the newly set value 117 | def login=(_); end 118 | 119 | # Returns the value of attribute password 120 | # 121 | # @return [Object] the current value of password 122 | def password; end 123 | 124 | # Sets the attribute password 125 | # 126 | # @param value [Object] the value to set the attribute password to. 127 | # @return [Object] the newly set value 128 | def password=(_); end 129 | 130 | def to_ary; end 131 | 132 | class << self 133 | def [](*_arg0); end 134 | def inspect; end 135 | def keyword_init?; end 136 | def members; end 137 | def new(*_arg0); end 138 | end 139 | end 140 | 141 | # source://netrc//lib/netrc.rb#250 142 | class Netrc::Error < ::StandardError; end 143 | 144 | # source://netrc//lib/netrc.rb#68 145 | class Netrc::TokenArray < ::Array 146 | # source://netrc//lib/netrc.rb#76 147 | def readto; end 148 | 149 | # source://netrc//lib/netrc.rb#69 150 | def take; end 151 | end 152 | 153 | # source://netrc//lib/netrc.rb#4 154 | Netrc::VERSION = T.let(T.unsafe(nil), String) 155 | 156 | # see http://stackoverflow.com/questions/4871309/what-is-the-correct-way-to-detect-if-ruby-is-running-on-windows 157 | # 158 | # source://netrc//lib/netrc.rb#7 159 | Netrc::WINDOWS = T.let(T.unsafe(nil), T.untyped) 160 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/rb-fsevent@0.11.2.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `rb-fsevent` gem. 5 | # Please instead update this file by running `bin/tapioca gem rb-fsevent`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/rb-inotify@0.11.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `rb-inotify` gem. 5 | # Please instead update this file by running `bin/tapioca gem rb-inotify`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/rspec-expectations@3.13.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `rspec-expectations` gem. 5 | # Please instead update this file by running `bin/tapioca gem rspec-expectations`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/rspec-sorbet@1.9.2.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `rspec-sorbet` gem. 5 | # Please instead update this file by running `bin/tapioca gem rspec-sorbet`. 6 | 7 | 8 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#6 9 | module RSpec 10 | class << self 11 | # source://rspec-core/3.13.0/lib/rspec/core.rb#70 12 | def clear_examples; end 13 | 14 | # source://rspec-core/3.13.0/lib/rspec/core.rb#85 15 | def configuration; end 16 | 17 | # source://rspec-core/3.13.0/lib/rspec/core.rb#49 18 | def configuration=(_arg0); end 19 | 20 | # source://rspec-core/3.13.0/lib/rspec/core.rb#97 21 | def configure; end 22 | 23 | # source://rspec-core/3.13.0/lib/rspec/core.rb#194 24 | def const_missing(name); end 25 | 26 | # source://rspec-core/3.13.0/lib/rspec/core/dsl.rb#42 27 | def context(*args, &example_group_block); end 28 | 29 | # source://rspec-core/3.13.0/lib/rspec/core.rb#122 30 | def current_example; end 31 | 32 | # source://rspec-core/3.13.0/lib/rspec/core.rb#128 33 | def current_example=(example); end 34 | 35 | # source://rspec-core/3.13.0/lib/rspec/core.rb#154 36 | def current_scope; end 37 | 38 | # source://rspec-core/3.13.0/lib/rspec/core.rb#134 39 | def current_scope=(scope); end 40 | 41 | # source://rspec-core/3.13.0/lib/rspec/core/dsl.rb#42 42 | def describe(*args, &example_group_block); end 43 | 44 | # source://rspec-core/3.13.0/lib/rspec/core/dsl.rb#42 45 | def example_group(*args, &example_group_block); end 46 | 47 | # source://rspec-core/3.13.0/lib/rspec/core/dsl.rb#42 48 | def fcontext(*args, &example_group_block); end 49 | 50 | # source://rspec-core/3.13.0/lib/rspec/core/dsl.rb#42 51 | def fdescribe(*args, &example_group_block); end 52 | 53 | # source://rspec-core/3.13.0/lib/rspec/core.rb#58 54 | def reset; end 55 | 56 | # source://rspec-core/3.13.0/lib/rspec/core/shared_example_group.rb#110 57 | def shared_context(name, *args, &block); end 58 | 59 | # source://rspec-core/3.13.0/lib/rspec/core/shared_example_group.rb#110 60 | def shared_examples(name, *args, &block); end 61 | 62 | # source://rspec-core/3.13.0/lib/rspec/core/shared_example_group.rb#110 63 | def shared_examples_for(name, *args, &block); end 64 | 65 | # source://rspec-core/3.13.0/lib/rspec/core.rb#160 66 | def world; end 67 | 68 | # source://rspec-core/3.13.0/lib/rspec/core.rb#49 69 | def world=(_arg0); end 70 | 71 | # source://rspec-core/3.13.0/lib/rspec/core/dsl.rb#42 72 | def xcontext(*args, &example_group_block); end 73 | 74 | # source://rspec-core/3.13.0/lib/rspec/core/dsl.rb#42 75 | def xdescribe(*args, &example_group_block); end 76 | end 77 | end 78 | 79 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#7 80 | module RSpec::Sorbet 81 | extend ::RSpec::Sorbet::Doubles 82 | end 83 | 84 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#8 85 | module RSpec::Sorbet::Doubles 86 | requires_ancestor { Kernel } 87 | 88 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#15 89 | sig { void } 90 | def allow_doubles!; end 91 | 92 | # @return [void] 93 | # 94 | # source://sorbet-runtime/0.5.11609/lib/types/private/methods/_methods.rb#257 95 | def allow_instance_doubles!(*args, **_arg1, &blk); end 96 | 97 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#36 98 | sig { params(clear_existing: T::Boolean).void } 99 | def reset!(clear_existing: T.unsafe(nil)); end 100 | 101 | private 102 | 103 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#139 104 | sig { params(signature: T.untyped, opts: T::Hash[T.untyped, T.untyped]).void } 105 | def call_validation_error_handler(signature, opts); end 106 | 107 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#65 108 | sig { returns(T.nilable(T::Boolean)) } 109 | def configured; end 110 | 111 | # @return [Boolean, nil] 112 | # 113 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#65 114 | def configured=(_arg0); end 115 | 116 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#127 117 | sig { params(message: ::String).returns(T::Boolean) } 118 | def double_message_with_ellipsis?(message); end 119 | 120 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#62 121 | sig { returns(T.nilable(T.proc.params(signature: T.untyped, opts: T::Hash[T.untyped, T.untyped]).void)) } 122 | def existing_call_validation_error_handler; end 123 | 124 | # @return [T.proc.params(signature: T.untyped, opts: T::Hash[T.untyped, T.untyped]).void, nil] 125 | # 126 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#62 127 | def existing_call_validation_error_handler=(_arg0); end 128 | 129 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#59 130 | sig { returns(T.nilable(T.proc.params(signature: ::Exception).void)) } 131 | def existing_inline_type_error_handler; end 132 | 133 | # @return [T.proc.params(signature: Exception).void, nil] 134 | # 135 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#59 136 | def existing_inline_type_error_handler=(_arg0); end 137 | 138 | # @raise [TypeError] 139 | # 140 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#73 141 | sig { params(signature: T.untyped, opts: T.untyped).void } 142 | def handle_call_validation_error(signature, opts); end 143 | 144 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#80 145 | sig { params(error: ::Exception).void } 146 | def inline_type_error_handler(error); end 147 | 148 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#134 149 | sig { params(message: ::String).returns(T::Boolean) } 150 | def typed_array_message?(message); end 151 | 152 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#118 153 | sig { params(message: ::String).returns(T::Boolean) } 154 | def unable_to_check_type_for_message?(message); end 155 | end 156 | 157 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#68 158 | RSpec::Sorbet::Doubles::INLINE_DOUBLE_REGEX = T.let(T.unsafe(nil), Regexp) 159 | 160 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#131 161 | RSpec::Sorbet::Doubles::TYPED_ARRAY_MESSAGE = T.let(T.unsafe(nil), Regexp) 162 | 163 | # source://rspec-sorbet//lib/rspec/sorbet/doubles.rb#123 164 | RSpec::Sorbet::Doubles::VERIFYING_DOUBLE_OR_DOUBLE = T.let(T.unsafe(nil), Regexp) 165 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/rspec.rbi: -------------------------------------------------------------------------------- 1 | # This file is autogenerated. Do not edit it by hand. Regenerate it with: 2 | # srb rbi gems 3 | 4 | # typed: strict 5 | # 6 | # If you would like to make changes to this file, great! Please create the gem's shim here: 7 | # 8 | # https://github.com/sorbet/sorbet-typed/new/master?filename=lib/rspec/all/rspec.rbi 9 | # 10 | # rspec-3.13.0 11 | 12 | module RSpec 13 | end 14 | module RSpec::Version 15 | end 16 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/rspec@3.13.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `rspec` gem. 5 | # Please instead update this file by running `bin/tapioca gem rspec`. 6 | 7 | 8 | # source://rspec//lib/rspec/version.rb#1 9 | module RSpec 10 | class << self 11 | # source://rspec-core/3.13.0/lib/rspec/core.rb#70 12 | def clear_examples; end 13 | 14 | # source://rspec-core/3.13.0/lib/rspec/core.rb#85 15 | def configuration; end 16 | 17 | # source://rspec-core/3.13.0/lib/rspec/core.rb#49 18 | def configuration=(_arg0); end 19 | 20 | # source://rspec-core/3.13.0/lib/rspec/core.rb#97 21 | def configure; end 22 | 23 | # source://rspec-core/3.13.0/lib/rspec/core.rb#194 24 | def const_missing(name); end 25 | 26 | # source://rspec-core/3.13.0/lib/rspec/core/dsl.rb#42 27 | def context(*args, &example_group_block); end 28 | 29 | # source://rspec-core/3.13.0/lib/rspec/core.rb#122 30 | def current_example; end 31 | 32 | # source://rspec-core/3.13.0/lib/rspec/core.rb#128 33 | def current_example=(example); end 34 | 35 | # source://rspec-core/3.13.0/lib/rspec/core.rb#154 36 | def current_scope; end 37 | 38 | # source://rspec-core/3.13.0/lib/rspec/core.rb#134 39 | def current_scope=(scope); end 40 | 41 | # source://rspec-core/3.13.0/lib/rspec/core/dsl.rb#42 42 | def describe(*args, &example_group_block); end 43 | 44 | # source://rspec-core/3.13.0/lib/rspec/core/dsl.rb#42 45 | def example_group(*args, &example_group_block); end 46 | 47 | # source://rspec-core/3.13.0/lib/rspec/core/dsl.rb#42 48 | def fcontext(*args, &example_group_block); end 49 | 50 | # source://rspec-core/3.13.0/lib/rspec/core/dsl.rb#42 51 | def fdescribe(*args, &example_group_block); end 52 | 53 | # source://rspec-core/3.13.0/lib/rspec/core.rb#58 54 | def reset; end 55 | 56 | # source://rspec-core/3.13.0/lib/rspec/core/shared_example_group.rb#110 57 | def shared_context(name, *args, &block); end 58 | 59 | # source://rspec-core/3.13.0/lib/rspec/core/shared_example_group.rb#110 60 | def shared_examples(name, *args, &block); end 61 | 62 | # source://rspec-core/3.13.0/lib/rspec/core/shared_example_group.rb#110 63 | def shared_examples_for(name, *args, &block); end 64 | 65 | # source://rspec-core/3.13.0/lib/rspec/core.rb#160 66 | def world; end 67 | 68 | # source://rspec-core/3.13.0/lib/rspec/core.rb#49 69 | def world=(_arg0); end 70 | 71 | # source://rspec-core/3.13.0/lib/rspec/core/dsl.rb#42 72 | def xcontext(*args, &example_group_block); end 73 | 74 | # source://rspec-core/3.13.0/lib/rspec/core/dsl.rb#42 75 | def xdescribe(*args, &example_group_block); end 76 | end 77 | end 78 | 79 | # source://rspec//lib/rspec/version.rb#2 80 | module RSpec::Version; end 81 | 82 | # source://rspec//lib/rspec/version.rb#3 83 | RSpec::Version::STRING = T.let(T.unsafe(nil), String) 84 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/safe_type.rbi: -------------------------------------------------------------------------------- 1 | # This file is autogenerated. Do not edit it by hand. Regenerate it with: 2 | # srb rbi gems 3 | 4 | # typed: strict 5 | # 6 | # If you would like to make changes to this file, great! Please create the gem's shim here: 7 | # 8 | # https://github.com/sorbet/sorbet-typed/new/master?filename=lib/safe_type/all/safe_type.rbi 9 | # 10 | # safe_type-1.1.1 11 | 12 | module SafeType 13 | def self.coerce!(input, rule); end 14 | def self.coerce(input, rule, coerce_key = nil); end 15 | end 16 | class SafeType::Converter 17 | def self.to_bool(input); end 18 | def self.to_date(input); end 19 | def self.to_date_time(input); end 20 | def self.to_false(input); end 21 | def self.to_float(input); end 22 | def self.to_int(input); end 23 | def self.to_time(input); end 24 | def self.to_true(input); end 25 | def self.to_type(input, type); end 26 | end 27 | class SafeType::CoercionError < StandardError 28 | def desired_type; end 29 | def initialize(value, desired_type, key = nil); end 30 | def key; end 31 | def value; end 32 | end 33 | class SafeType::ValidationError < StandardError 34 | def desired_type; end 35 | def initialize(value, desired_type, key = nil); end 36 | def key; end 37 | def value; end 38 | end 39 | class SafeType::EmptyValueError < StandardError 40 | def desired_type; end 41 | def initialize(desired_type, key = nil); end 42 | def key; end 43 | end 44 | class SafeType::InvalidRuleError < ArgumentError 45 | def initialize; end 46 | end 47 | class SafeType::Rule 48 | def after(input); end 49 | def before(input); end 50 | def coerce(input, key = nil); end 51 | def initialize(type:, default: nil, required: nil, **args); end 52 | def is_valid?(input); end 53 | def self.coerce(input); end 54 | def self.default; end 55 | def self.strict; end 56 | end 57 | module SafeType::BooleanMixin 58 | end 59 | class TrueClass 60 | include SafeType::BooleanMixin 61 | end 62 | class FalseClass 63 | include SafeType::BooleanMixin 64 | end 65 | class SafeType::Boolean < SafeType::Rule 66 | def initialize(type: nil, **args); end 67 | def self.default(value = nil); end 68 | end 69 | class SafeType::Date < SafeType::Rule 70 | def initialize(type: nil, from: nil, to: nil, **args); end 71 | def is_valid?(input); end 72 | def self.default(value = nil, from: nil, to: nil); end 73 | def self.strict(from: nil, to: nil); end 74 | end 75 | class SafeType::DateTime < SafeType::Rule 76 | def initialize(type: nil, from: nil, to: nil, **args); end 77 | def is_valid?(input); end 78 | def self.default(value = nil, from: nil, to: nil); end 79 | def self.strict(from: nil, to: nil); end 80 | end 81 | class SafeType::Float < SafeType::Rule 82 | def initialize(type: nil, min: nil, max: nil, **args); end 83 | def is_valid?(input); end 84 | def self.default(value = nil, min: nil, max: nil); end 85 | def self.strict(min: nil, max: nil); end 86 | end 87 | class SafeType::Integer < SafeType::Rule 88 | def initialize(type: nil, min: nil, max: nil, **args); end 89 | def is_valid?(input); end 90 | def self.default(value = nil, min: nil, max: nil); end 91 | def self.strict(min: nil, max: nil); end 92 | end 93 | class SafeType::String < SafeType::Rule 94 | def after(input); end 95 | def initialize(type: nil, min_length: nil, max_length: nil, **args); end 96 | def is_valid?(input); end 97 | def self.default(value = nil, min_length: nil, max_length: nil); end 98 | def self.strict(min_length: nil, max_length: nil); end 99 | end 100 | class SafeType::Symbol < SafeType::Rule 101 | def after(input); end 102 | def initialize(type: nil, min_length: nil, max_length: nil, **args); end 103 | def is_valid?(input); end 104 | def self.default(value = nil, min_length: nil, max_length: nil); end 105 | def self.strict(min_length: nil, max_length: nil); end 106 | end 107 | class SafeType::Time < SafeType::Rule 108 | def initialize(type: nil, from: nil, to: nil, **args); end 109 | def is_valid?(input); end 110 | def self.default(value = nil, from: nil, to: nil); end 111 | def self.strict(from: nil, to: nil); end 112 | end 113 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/shellany@0.0.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `shellany` gem. 5 | # Please instead update this file by running `bin/tapioca gem shellany`. 6 | 7 | 8 | # source://shellany//lib/shellany/sheller.rb#3 9 | module Shellany; end 10 | 11 | # The Guard sheller abstract the actual subshell 12 | # calls and allow easier stubbing. 13 | # 14 | # source://shellany//lib/shellany/sheller.rb#7 15 | class Shellany::Sheller 16 | # Creates a new Guard::Sheller object. 17 | # 18 | # @param args [String] a command to run in a subshell 19 | # @param args [Array] an array of command parts to run in a subshell 20 | # @param args [*String] a list of command parts to run in a subshell 21 | # @return [Sheller] a new instance of Sheller 22 | # 23 | # source://shellany//lib/shellany/sheller.rb#16 24 | def initialize(*args); end 25 | 26 | # Returns true if the command succeeded, false otherwise. 27 | # 28 | # @return [Boolean] whether or not the command succeeded 29 | # 30 | # source://shellany//lib/shellany/sheller.rb#68 31 | def ok?; end 32 | 33 | # Returns true if the command has already been run, false otherwise. 34 | # 35 | # @return [Boolean] whether or not the command has already been run 36 | # 37 | # source://shellany//lib/shellany/sheller.rb#60 38 | def ran?; end 39 | 40 | # Runs the command. 41 | # 42 | # @return [Boolean] whether or not the command succeeded. 43 | # 44 | # source://shellany//lib/shellany/sheller.rb#44 45 | def run; end 46 | 47 | # Returns the value of attribute status. 48 | # 49 | # source://shellany//lib/shellany/sheller.rb#8 50 | def status; end 51 | 52 | # Returns the command's error output. 53 | # 54 | # @return [String] the command output 55 | # 56 | # source://shellany//lib/shellany/sheller.rb#88 57 | def stderr; end 58 | 59 | # Returns the command's output. 60 | # 61 | # @return [String] the command output 62 | # 63 | # source://shellany//lib/shellany/sheller.rb#78 64 | def stdout; end 65 | 66 | class << self 67 | # Only needed on JRUBY, because MRI properly detects ';' and metachars 68 | # 69 | # source://shellany//lib/shellany/sheller.rb#128 70 | def _shellize_if_needed(args); end 71 | 72 | # source://shellany//lib/shellany/sheller.rb#110 73 | def _system_with_capture(*args); end 74 | 75 | # source://shellany//lib/shellany/sheller.rb#103 76 | def _system_with_no_capture(*args); end 77 | 78 | # Shortcut for new(command).run 79 | # 80 | # source://shellany//lib/shellany/sheller.rb#24 81 | def run(*args); end 82 | 83 | # Shortcut for new(command).run.stderr 84 | # 85 | # source://shellany//lib/shellany/sheller.rb#36 86 | def stderr(*args); end 87 | 88 | # Shortcut for new(command).run.stdout 89 | # 90 | # source://shellany//lib/shellany/sheller.rb#30 91 | def stdout(*args); end 92 | 93 | # No output capturing 94 | # 95 | # NOTE: `$stdout.puts system('cls')` on Windows won't work like 96 | # it does for on systems with ansi terminals, so we need to be 97 | # able to call Kernel.system directly. 98 | # 99 | # source://shellany//lib/shellany/sheller.rb#99 100 | def system(*args); end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/sorbet-coerce.rbi: -------------------------------------------------------------------------------- 1 | # This file is autogenerated. Do not edit it by hand. Regenerate it with: 2 | # srb rbi gems 3 | 4 | # typed: false 5 | # 6 | # If you would like to make changes to this file, great! Please create the gem's shim here: 7 | # 8 | # https://github.com/sorbet/sorbet-typed/new/master?filename=lib/sorbet-coerce/all/sorbet-coerce.rbi 9 | # 10 | # sorbet-coerce-0.7.0 11 | 12 | module TypeCoerce 13 | def self.[](type); end 14 | end 15 | module TypeCoerce::Configuration 16 | def self.raise_coercion_error(*args, **, &blk); end 17 | def self.raise_coercion_error=(arg0); end 18 | end 19 | module Polyfill::Module::MezpIYXNoPT5bIiNzbGljZSJdfQ__ 20 | end 21 | class TypeCoerce::CoercionError < SafeType::CoercionError 22 | end 23 | class TypeCoerce::ShapeError < SafeType::CoercionError 24 | end 25 | class TypeCoerce::Converter 26 | def _build_args(args, type, raise_coercion_error, coerce_empty_to_nil); end 27 | def _convert(value, type, raise_coercion_error, coerce_empty_to_nil); end 28 | def _convert_enum(value, type, raise_coercion_error, coerce_empty_to_nil); end 29 | def _convert_simple(value, type, raise_coercion_error, coerce_empty_to_nil); end 30 | def _convert_to_a(ary, type, raise_coercion_error, coerce_empty_to_nil); end 31 | def _nil_like?(value, type, coerce_empty_to_nil); end 32 | def coerce_nil(value, type, coerce_empty_to_nil); end 33 | def from(args, raise_coercion_error: nil, coerce_empty_to_nil: nil); end 34 | def initialize(type); end 35 | def new; end 36 | def to_s; end 37 | end 38 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/sorbet-coerce@0.7.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: false 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `sorbet-coerce` gem. 5 | # Please instead update this file by running `bin/tapioca gem sorbet-coerce`. 6 | 7 | 8 | # source://sorbet-coerce//lib/sorbet-coerce/configuration.rb#4 9 | module TypeCoerce 10 | class << self 11 | # source://sorbet-coerce//lib/sorbet-coerce.rb#5 12 | def [](type); end 13 | end 14 | end 15 | 16 | # source://sorbet-coerce//lib/sorbet-coerce/converter.rb#11 17 | class TypeCoerce::CoercionError < ::SafeType::CoercionError; end 18 | 19 | # source://sorbet-coerce//lib/sorbet-coerce/configuration.rb#5 20 | module TypeCoerce::Configuration 21 | class << self 22 | # source://sorbet-coerce//lib/sorbet-coerce/configuration.rb#10 23 | sig { returns(T::Boolean) } 24 | def raise_coercion_error; end 25 | 26 | # @return [Boolean] 27 | # 28 | # source://sorbet-coerce//lib/sorbet-coerce/configuration.rb#10 29 | def raise_coercion_error=(_arg0); end 30 | end 31 | end 32 | 33 | # source://sorbet-coerce//lib/sorbet-coerce/converter.rb#15 34 | class TypeCoerce::Converter 35 | # @return [Converter] a new instance of Converter 36 | # 37 | # source://sorbet-coerce//lib/sorbet-coerce/converter.rb#16 38 | def initialize(type); end 39 | 40 | # source://sorbet-coerce//lib/sorbet-coerce/converter.rb#28 41 | def from(args, raise_coercion_error: T.unsafe(nil), coerce_empty_to_nil: T.unsafe(nil)); end 42 | 43 | # source://sorbet-coerce//lib/sorbet-coerce/converter.rb#20 44 | def new; end 45 | 46 | # source://sorbet-coerce//lib/sorbet-coerce/converter.rb#24 47 | def to_s; end 48 | 49 | private 50 | 51 | # source://sorbet-coerce//lib/sorbet-coerce/converter.rb#176 52 | def _build_args(args, type, raise_coercion_error, coerce_empty_to_nil); end 53 | 54 | # source://sorbet-coerce//lib/sorbet-coerce/converter.rb#48 55 | def _convert(value, type, raise_coercion_error, coerce_empty_to_nil); end 56 | 57 | # source://sorbet-coerce//lib/sorbet-coerce/converter.rb#120 58 | def _convert_enum(value, type, raise_coercion_error, coerce_empty_to_nil); end 59 | 60 | # source://sorbet-coerce//lib/sorbet-coerce/converter.rb#130 61 | def _convert_simple(value, type, raise_coercion_error, coerce_empty_to_nil); end 62 | 63 | # source://sorbet-coerce//lib/sorbet-coerce/converter.rb#160 64 | def _convert_to_a(ary, type, raise_coercion_error, coerce_empty_to_nil); end 65 | 66 | # @return [Boolean] 67 | # 68 | # source://sorbet-coerce//lib/sorbet-coerce/converter.rb#194 69 | def _nil_like?(value, type, coerce_empty_to_nil); end 70 | 71 | # source://sorbet-coerce//lib/sorbet-coerce/converter.rb#202 72 | def coerce_nil(value, type, coerce_empty_to_nil); end 73 | end 74 | 75 | # source://sorbet-coerce//lib/sorbet-coerce/converter.rb#38 76 | TypeCoerce::Converter::PRIMITIVE_TYPES = T.let(T.unsafe(nil), Set) 77 | 78 | # source://sorbet-coerce//lib/sorbet-coerce/converter.rb#12 79 | class TypeCoerce::ShapeError < ::SafeType::CoercionError; end 80 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/tzinfo@2.0.6.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `tzinfo` gem. 5 | # Please instead update this file by running `bin/tapioca gem tzinfo`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/shims/rails.rbi: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module Rails 4 | sig { returns(T.untyped) } 5 | def self.root 6 | end 7 | 8 | class Engine 9 | end 10 | end 11 | 12 | module ActionDispatch 13 | module Http 14 | class UploadedFile 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /sorbet/rbi/shims/repositories.rbi: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | # FIXME: This is a patch for some Factorial abstractions that haven't quite been 4 | # removed from the gem yet. Once a clearer boundary is established, this should 5 | # be removed. 6 | module Repositories 7 | module ReadResult 8 | end 9 | class InMemoryReadResult 10 | end 11 | module ReadOutputContext 12 | end 13 | module OutputContexts 14 | class Filter 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /sorbet/tapioca/config.yml: -------------------------------------------------------------------------------- 1 | gem: 2 | # Add your `gem` command parameters here: 3 | # 4 | # exclude: 5 | # - gem_name 6 | # doc: true 7 | # workers: 5 8 | dsl: 9 | # Add your `dsl` command parameters here: 10 | # 11 | # exclude: 12 | # - SomeGeneratorName 13 | # workers: 5 14 | -------------------------------------------------------------------------------- /sorbet/tapioca/require.rb: -------------------------------------------------------------------------------- 1 | # typed: true 2 | # frozen_string_literal: true 3 | 4 | require "active_support/all" 5 | require "bigdecimal" 6 | require "date" 7 | require "pry" 8 | require "rbi" 9 | require "rspec" 10 | require "sorbet-coerce" 11 | require "sorbet-runtime" 12 | -------------------------------------------------------------------------------- /spec/capability_factory_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: false 3 | 4 | require "spec_helper" 5 | require_relative "./dummy_capability" 6 | require_relative "../lib/resource_registry/capability_factory" 7 | 8 | RSpec.describe ResourceRegistry::CapabilityFactory do 9 | let(:capability) { described_class } 10 | let(:capability_config) { DummyCapability.new(time_dimension: "foo") } 11 | 12 | describe "#dump" do 13 | it do 14 | expect(capability.dump(capability_config)).to include_json( 15 | time_dimension: "foo" 16 | ) 17 | end 18 | end 19 | 20 | describe "#load" do 21 | let(:data) { { "key" => "dummy_capability", :time_dimension => "bar" } } 22 | 23 | it do 24 | cap = 25 | capability.load( 26 | data, 27 | capabilities: { 28 | dummy_capability: DummyCapability 29 | } 30 | ) 31 | expect(described_class.dump(cap)).to include_json(time_dimension: "bar") 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/dummy_capability.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: strict 3 | 4 | class DummyCapability < T::Struct 5 | extend T::Sig 6 | 7 | include ResourceRegistry::Capabilities::CapabilityConfig 8 | 9 | const :time_dimension, T.nilable(String), default: "day" 10 | 11 | sig { override.returns(Symbol) } 12 | def self.key 13 | :dummy_capability 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/dummy_repo.rb: -------------------------------------------------------------------------------- 1 | # typed: false 2 | 3 | require_relative "../lib/resource_registry/repositories/base" 4 | 5 | class DummyEntity < T::Struct 6 | const :id, Integer 7 | end 8 | 9 | class DummyRepo 10 | extend T::Sig 11 | extend T::Helpers 12 | extend T::Generic 13 | 14 | include ResourceRegistry::Repositories::Base 15 | 16 | Entity = type_member { { upper: DummyEntity } } 17 | 18 | sig do 19 | params(dto: T::Struct, context: T.untyped).returns( 20 | ::Repositories::ReadResult[Entity] 21 | ) 22 | end 23 | def read(dto:, context: ::Repositories::ReadOutputContext.new) 24 | entities = (1..10).map { |i| DummyEntity.new(id: i) } 25 | Repositories::InMemoryReadResult.new(context: context.new, list: entities) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/resource_registry/capabilities/capability_config_spec.rb: -------------------------------------------------------------------------------- 1 | # typed: false 2 | 3 | require "spec_helper" 4 | require_relative "../../../lib/resource_registry/capabilities/capability_config" 5 | 6 | class DummyCapability < T::Struct 7 | include ResourceRegistry::Capabilities::CapabilityConfig 8 | 9 | def self.key 10 | :dummy_capability 11 | end 12 | end 13 | 14 | RSpec.describe ResourceRegistry::Capabilities::CapabilityConfig do 15 | let(:schema) do 16 | SchemaRegistry::Schema.new( 17 | name: "dummy", 18 | namespace: "dummies", 19 | properties: [ 20 | SchemaRegistry::Property.new( 21 | name: "foo", 22 | types: [SchemaRegistry::PropertyType::String], 23 | required: true 24 | ) 25 | ] 26 | ) 27 | end 28 | let(:capabilities) { { dummy_capability: DummyCapability.new } } 29 | let(:resource) do 30 | ResourceRegistry::Resource.new( 31 | repository_raw: DummyRepo.to_s, 32 | capabilities:, 33 | verbs: { 34 | }, 35 | schema: 36 | ) 37 | end 38 | 39 | it "should return resource's capability" do 40 | expect(DummyCapability.resource_capability?(resource:)).to be true 41 | expect(DummyCapability.resource_capability(resource:)).to be_a(DummyCapability) 42 | expect(DummyCapability.resource_capability!(resource:)).to be_a(DummyCapability) 43 | end 44 | 45 | context 'without the capability' do 46 | let(:capabilities) { {} } 47 | 48 | it "should return resource's capability" do 49 | expect(DummyCapability.resource_capability?(resource:)).to be false 50 | expect(DummyCapability.resource_capability(resource:)).to be_nil 51 | end 52 | end 53 | end 54 | 55 | 56 | -------------------------------------------------------------------------------- /spec/resource_registry/registry_spec.rb: -------------------------------------------------------------------------------- 1 | # typed: false 2 | 3 | require "spec_helper" 4 | require_relative "../../lib/resource_registry/registry" 5 | require_relative "../dummy_repo" 6 | 7 | class DummyCapability < T::Struct 8 | include ResourceRegistry::Capabilities::CapabilityConfig 9 | 10 | def self.key 11 | :dummy_capability 12 | end 13 | end 14 | 15 | class VoidCapability < T::Struct 16 | include ResourceRegistry::Capabilities::CapabilityConfig 17 | 18 | def self.key 19 | :void_capability 20 | end 21 | end 22 | 23 | RSpec.describe ResourceRegistry::Registry do 24 | let(:resources) { [resource] } 25 | let(:registry) { described_class.new(resources: resources) } 26 | let(:schema) do 27 | SchemaRegistry::Schema.new( 28 | name: "employees", 29 | namespace: "employees", 30 | properties: [ 31 | SchemaRegistry::Property.new( 32 | name: "foo", 33 | types: [SchemaRegistry::PropertyType::String], 34 | required: true 35 | ) 36 | ] 37 | ) 38 | end 39 | let(:resource) do 40 | ResourceRegistry::Resource.new( 41 | repository_raw: DummyRepo.to_s, 42 | capabilities: { 43 | dummy_capability: DummyCapability.new 44 | }, 45 | verbs: { 46 | }, 47 | schema: schema 48 | ) 49 | end 50 | let(:identifier) { resource.identifier.to_s } 51 | 52 | describe "#initialize" do 53 | let(:resources) { [resource, resource] } 54 | 55 | it "not allow duplicated identifiers" do 56 | expect { registry }.to raise_error( 57 | ResourceRegistry::Registry::DuplicatedIdentifierError 58 | ) 59 | end 60 | end 61 | 62 | describe "#fetch" do 63 | it { expect(registry.fetch(identifier)).to be(resource) } 64 | end 65 | 66 | describe "#fetch_all" do 67 | it { expect(registry.fetch_all).to eq({ identifier => resource }) } 68 | end 69 | 70 | describe "#fetch_for_repository" do 71 | it { expect(registry.fetch_for_repository(DummyRepo)).to(eq(resource)) } 72 | end 73 | 74 | describe "#fetch_with_capabilities" do 75 | let(:features) { [DummyCapability] } 76 | 77 | subject { registry.fetch_with_capabilities(*features) } 78 | 79 | it { expect(subject.size).to eq(1) } 80 | 81 | context 'the resource doesn\t have the capability' do 82 | let(:features) { [VoidCapability] } 83 | 84 | it { expect(subject.size).to eq(0) } 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /spec/resource_registry/relationship_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: false 3 | 4 | require "spec_helper" 5 | require_relative "../../lib/resource_registry/relationship" 6 | 7 | RSpec.describe ResourceRegistry::Relationship do 8 | describe "#load" do 9 | let(:params) do 10 | { 11 | "name" => "test", 12 | "type" => "has_one", 13 | "field" => :test, 14 | "resource_id" => :test, 15 | "optional" => true, 16 | "primary_key" => :test 17 | } 18 | end 19 | 20 | subject { described_class.load(params) } 21 | 22 | it { expect(subject).to be_a(described_class) } 23 | 24 | context "when the spec is not valid" do 25 | let(:params) { { "type" => "has_one" } } 26 | 27 | it "raises an error" do 28 | expect { subject }.to raise_error( 29 | ResourceRegistry::RelationshipType::InvalidRelationshipSpec 30 | ) 31 | end 32 | end 33 | 34 | # TODO: Check how to test this 35 | xcontext "with a custom type" do 36 | let(:type) { "policy_resolution" } 37 | let(:params) do 38 | { 39 | "name" => "test", 40 | "type" => type, 41 | "field" => :test, 42 | "resource_id" => :test, 43 | "optional" => true, 44 | "policies" => [] 45 | } 46 | end 47 | 48 | it { expect(subject).to be_a(described_class) } 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/resource_registry/resource_spec.rb: -------------------------------------------------------------------------------- 1 | # typed: false 2 | 3 | require "spec_helper" 4 | require_relative "../../lib/resource_registry/resource" 5 | require_relative "../dummy_repo" 6 | require_relative "../dummy_capability" 7 | require_relative "../void_capability" 8 | 9 | RSpec.describe ResourceRegistry::Resource do 10 | let(:capability) { DummyCapability.new } 11 | let(:dummy_struct) { T::Struct } 12 | let(:schema) do 13 | SchemaRegistry::Schema.new( 14 | name: "employees", 15 | namespace: "employees", 16 | properties: [ 17 | SchemaRegistry::Property.new( 18 | name: "foo", 19 | types: [SchemaRegistry::PropertyType::String], 20 | required: true 21 | ) 22 | ] 23 | ) 24 | end 25 | let(:verbs) do 26 | { 27 | copy: 28 | ResourceRegistry::Verb.new( 29 | id: :copy, 30 | dto_raw: dummy_struct.to_s, 31 | schema: schema 32 | ) 33 | } 34 | end 35 | let(:resource) do 36 | ResourceRegistry::Resource.new( 37 | repository_raw: DummyRepo.to_s, 38 | description: "foo", 39 | schema: schema, 40 | verbs: verbs, 41 | capabilities: { 42 | dummy_capability: capability 43 | } 44 | ) 45 | end 46 | 47 | it { expect(resource.schema).to be_a(SchemaRegistry::Schema) } 48 | it { expect(resource.schema.properties.first.name).to eq "foo" } 49 | 50 | describe "#collection_name" do 51 | it { expect(resource.collection_name).to eq("dummy_repos") } 52 | end 53 | 54 | describe "#path" do 55 | it { expect(resource.path).to eq("dummyrepo/dummy_repo") } 56 | end 57 | 58 | describe "#dump" do 59 | it { expect(resource.dump).to include_json(description: "foo") } 60 | end 61 | 62 | describe "#load" do 63 | let(:spec) { resource.dump } 64 | let(:configuration) do 65 | ResourceRegistry::Configuration.new.tap do |conf| 66 | conf.register_capability(:dummy_capability, DummyCapability) 67 | end 68 | end 69 | 70 | subject { described_class.load(spec, configuration: configuration) } 71 | 72 | it { expect(subject).to be_a(described_class) } 73 | 74 | context "when verb has no id" do 75 | let(:dump) { resource.dump } 76 | let(:spec) { resource.dump.tap { |s| s["verbs"]["copy"].delete("id") } } 77 | 78 | it { expect { subject }.to raise_error(ArgumentError) } 79 | end 80 | 81 | describe "paginateable" do 82 | context "when the paginateable property is not provided" do 83 | it "is true by default" do 84 | expect(subject.paginateable).to be true 85 | end 86 | end 87 | 88 | context "when the paginateable property is provided" do 89 | before { spec["paginateable"] = false } 90 | 91 | it "uses the value from the resource definition" do 92 | expect(subject.paginateable).to be false 93 | end 94 | end 95 | end 96 | 97 | describe "#capability!" do 98 | let(:feature) { DummyCapability } 99 | 100 | subject { resource.capability!(feature) } 101 | 102 | it { expect(subject).to eq(capability) } 103 | 104 | context 'The resource don\'t have such capability' do 105 | let(:feature) { VoidCapability } 106 | 107 | it { expect { subject }.to raise_error(ArgumentError) } 108 | end 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /spec/resource_registry/versions_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: false 3 | 4 | require "spec_helper" 5 | require_relative "../../lib/resource_registry/versions" 6 | 7 | RSpec.describe ResourceRegistry::Versions do 8 | subject { described_class.new(versions: versions) } 9 | 10 | let(:versions) do 11 | [ 12 | ResourceRegistry::Versions::Version.new("2024-01-01"), 13 | ResourceRegistry::Versions::Version.new("2024-04-01", aliases: "stable") 14 | ] 15 | end 16 | 17 | describe "#find!" do 18 | it "returns a version" do 19 | expect(subject.find!("2024-04-01").to_s).to eq("2024-04-01") 20 | end 21 | 22 | it "raises error with wrong name" do 23 | expect { subject.find!("fake") }.to raise_error( 24 | RuntimeError, 25 | 'Version \'fake\' not found' 26 | ) 27 | end 28 | end 29 | 30 | describe "#find" do 31 | it "finds a version from right name" do 32 | expect(subject.find("2024-04-01").to_s).to eq("2024-04-01") 33 | end 34 | 35 | it "does not find a version from wrong name" do 36 | expect(subject.find("unknown")).to be_nil 37 | end 38 | 39 | it "does not find a version from empty header" do 40 | expect(subject.find(nil)).to be_nil 41 | expect(subject.find("")).to be_nil 42 | end 43 | 44 | it "returns a version with an alias" do 45 | expect(subject.find!("stable").to_s).to eq("2024-04-01") 46 | end 47 | end 48 | 49 | describe "#find_next" do 50 | context "when given an old version" do 51 | it "returns the following version" do 52 | expect(subject.find_next("2024-01-01").to_s).to eq("2024-04-01") 53 | end 54 | 55 | context "when given a version object" do 56 | it "returns the following version" do 57 | version = subject.find!("2024-01-01") 58 | 59 | expect(subject.find_next(version).to_s).to eq("2024-04-01") 60 | end 61 | end 62 | end 63 | 64 | context "when given the last version" do 65 | it "does not returns a version" do 66 | expect(subject.find_next("2024-04-01")).to be_nil 67 | end 68 | 69 | context "when given a version object" do 70 | it "does not returns a version" do 71 | version = subject.find!("2024-04-01") 72 | 73 | expect(subject.find_next(version)).to be_nil 74 | end 75 | end 76 | end 77 | 78 | context "when given a random value" do 79 | it "does not returns a version" do 80 | expect { subject.find_next("2025-04-01") }.to raise_error( 81 | RuntimeError, 82 | 'Version \'2025-04-01\' not found' 83 | ) 84 | end 85 | end 86 | 87 | context "when given a list of unsorted versions" do 88 | let(:versions) do 89 | [ 90 | ResourceRegistry::Versions::Version.new( 91 | "2024-03-01", 92 | aliases: "stable" 93 | ), 94 | ResourceRegistry::Versions::Version.new( 95 | "2024-04-01", 96 | aliases: "deprecated" 97 | ), 98 | ResourceRegistry::Versions::Version.new("2024-02-01") 99 | ] 100 | end 101 | 102 | it "orders and returns the following version" do 103 | expect(subject.find_next("2024-02-01").to_s).to eq("2024-03-01") 104 | end 105 | end 106 | end 107 | 108 | describe "#in_range" do 109 | let(:versions) do 110 | [ 111 | ResourceRegistry::Versions::Version.new("2024-01-01"), 112 | ResourceRegistry::Versions::Version.new("2024-04-28"), 113 | ResourceRegistry::Versions::Version.new("2024-09-20"), 114 | ResourceRegistry::Versions::Version.new("2025-01-09") 115 | ] 116 | end 117 | 118 | it "filters versions by >= from and <= to" do 119 | expect(subject.in_range("2024-04-28", "2024-09-20").count).to eq(2) 120 | end 121 | 122 | context "with only from" do 123 | it "filters versions by >= from" do 124 | expect(subject.in_range("2024-04-28", nil).count).to eq(3) 125 | end 126 | end 127 | 128 | context "with only to" do 129 | it "filters versions by <= to" do 130 | expect(subject.in_range(nil, "2024-09-20").count).to eq(3) 131 | end 132 | end 133 | 134 | context "with unexisting version" do 135 | it "raises error with wrong name" do 136 | expect { subject.in_range("2022-01-01", "2022-01-01") }.to raise_error( 137 | RuntimeError, 138 | 'Version \'2022-01-01\' not found' 139 | ) 140 | end 141 | end 142 | 143 | context "without from and to" do 144 | it "does not apply any filter" do 145 | expect(subject.in_range(nil, nil).count).to eq(4) 146 | end 147 | end 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /spec/schema_registry/maybe_spec.rb: -------------------------------------------------------------------------------- 1 | # typed: false 2 | 3 | require "spec_helper" # you are forced to load the whole context for this :/ 4 | require_relative "../../lib/schema_registry/maybe" 5 | 6 | RSpec.describe Maybe do 7 | let(:present) { Maybe.from(6) } 8 | let(:nilable) { Maybe.from(nil) } 9 | let(:absent) { Maybe.empty } 10 | 11 | describe "#strip" do 12 | let(:input) { { present: Maybe.from(6), absent: Maybe.empty, always: 5 } } 13 | 14 | subject { Maybe.strip(input) } 15 | 16 | it "removes all absent values, unwraps present `Maybe`s" do 17 | expect(subject).to match({ present: 6, always: 5 }) 18 | end 19 | end 20 | 21 | describe "#present?" do 22 | subject { maybe.present? } 23 | 24 | context "when a value is present" do 25 | let(:maybe) { present } 26 | 27 | it "returns true" do 28 | expect(subject).to be true 29 | end 30 | end 31 | 32 | context "when a `nil` value is present" do 33 | let(:maybe) { nilable } 34 | 35 | it "returns true" do 36 | expect(subject).to be true 37 | end 38 | end 39 | 40 | context "when a value is not present" do 41 | let(:maybe) { absent } 42 | 43 | it "returns false" do 44 | expect(subject).to be false 45 | end 46 | end 47 | end 48 | 49 | describe "#absent?" do 50 | subject { maybe.absent? } 51 | 52 | context "when a value is present" do 53 | let(:maybe) { present } 54 | 55 | it "returns false" do 56 | expect(subject).to be false 57 | end 58 | end 59 | 60 | context "when a `nil` value is present" do 61 | let(:maybe) { nilable } 62 | 63 | it "returns false" do 64 | expect(subject).to be false 65 | end 66 | end 67 | 68 | context "when a value is not present" do 69 | let(:maybe) { absent } 70 | 71 | it "returns true" do 72 | expect(subject).to be true 73 | end 74 | end 75 | end 76 | 77 | describe "#or_default" do 78 | subject { maybe.or_default(default) } 79 | 80 | let(:default) { "1337" } 81 | 82 | context "when a value is present" do 83 | let(:maybe) { present } 84 | 85 | it "returns the contained value" do 86 | expect(subject).to be 6 87 | end 88 | end 89 | 90 | context "when a `nil` value is present" do 91 | let(:maybe) { nilable } 92 | 93 | it "returns the contained value" do 94 | expect(subject).to be_nil 95 | end 96 | end 97 | 98 | context "when a value is not present" do 99 | let(:maybe) { absent } 100 | 101 | it "returns the default value" do 102 | expect(subject).to be default 103 | end 104 | end 105 | end 106 | 107 | describe "#when_present" do 108 | subject { maybe.when_present { |v| (v || 0) + 1 } } 109 | 110 | context "when a value is present" do 111 | let(:maybe) { present } 112 | 113 | it "returns the value returned from the block" do 114 | expect(subject).to be 7 115 | end 116 | end 117 | 118 | context "when a `nil` value is present" do 119 | let(:maybe) { nilable } 120 | 121 | it "returns the value returned from the block" do 122 | expect(subject).to be 1 123 | end 124 | end 125 | 126 | context "when a value is not present" do 127 | let(:maybe) { absent } 128 | 129 | it "returns nil" do 130 | expect(subject).to be_nil 131 | end 132 | end 133 | end 134 | 135 | describe "#when_absent" do 136 | subject { maybe.when_absent { 1 + 1 } } 137 | 138 | context "when a value is present" do 139 | let(:maybe) { present } 140 | 141 | it "returns nil" do 142 | expect(subject).to be_nil 143 | end 144 | end 145 | 146 | context "when a `nil` value is present" do 147 | let(:maybe) { nilable } 148 | 149 | it "returns nil" do 150 | expect(subject).to be_nil 151 | end 152 | end 153 | 154 | context "when a value is not present" do 155 | let(:maybe) { absent } 156 | 157 | it "returns the value returned from the block" do 158 | expect(subject).to be 2 159 | end 160 | end 161 | end 162 | 163 | describe "#filter" do 164 | subject { maybe.filter { |_e| filter_output } } 165 | 166 | context "when the filter returns false" do 167 | let(:filter_output) { false } 168 | 169 | context "and a value is present" do 170 | let(:maybe) { present } 171 | 172 | it "returns an empty instance" do 173 | expect(subject.absent?).to be true 174 | end 175 | end 176 | 177 | context "and a `nil` value is present" do 178 | let(:maybe) { nilable } 179 | 180 | it "returns an empty instance" do 181 | expect(subject.absent?).to be true 182 | end 183 | end 184 | 185 | context "and a value is not present" do 186 | let(:maybe) { absent } 187 | 188 | it "returns an empty instance" do 189 | expect(subject.absent?).to be true 190 | end 191 | end 192 | end 193 | 194 | context "when the filter returns true" do 195 | let(:filter_output) { true } 196 | 197 | context "and a value is present" do 198 | let(:maybe) { present } 199 | 200 | it "returns the same instance" do 201 | expect(subject).to be maybe 202 | end 203 | end 204 | 205 | context "and a `nil` value is present" do 206 | let(:maybe) { nilable } 207 | 208 | it "returns the same instance" do 209 | expect(subject).to be maybe 210 | end 211 | end 212 | 213 | context "and a value is not present" do 214 | let(:maybe) { absent } 215 | 216 | it "returns the same instance" do 217 | expect(subject).to be maybe 218 | end 219 | end 220 | end 221 | end 222 | 223 | describe "#map" do 224 | subject { maybe.map { |e| (e || 0) + 1 } } 225 | 226 | context "when a value is present" do 227 | let(:maybe) { present } 228 | 229 | it "returns an instance containing the result of the block" do 230 | expect(subject.value).to eq Maybe.from(7).value 231 | end 232 | end 233 | 234 | context "when a `nil` value is present" do 235 | let(:maybe) { nilable } 236 | 237 | it "returns an instance containing the result of the block" do 238 | expect(subject.value).to eq Maybe.from(1).value 239 | end 240 | end 241 | 242 | context "when a value is not present" do 243 | let(:maybe) { absent } 244 | 245 | it "returns the same instance" do 246 | expect(subject).to be maybe 247 | end 248 | end 249 | end 250 | 251 | describe "#==" do 252 | subject { first == second } 253 | 254 | context "when both have values" do 255 | context "and the values are the same" do 256 | let(:first) { Maybe.from("Potato") } 257 | let(:second) { Maybe.from("Potato") } 258 | 259 | it("returns true") { expect(subject).to be true } 260 | end 261 | 262 | context "and the values are different" do 263 | let(:first) { Maybe.from("Tomato") } 264 | let(:second) { Maybe.from("Carrot") } 265 | 266 | it("returns false") { expect(subject).to be false } 267 | end 268 | end 269 | 270 | context "when only one has values" do 271 | let(:first) { Maybe.from("Apricot") } 272 | let(:second) { Maybe.empty } 273 | 274 | it("returns false") { expect(subject).to be false } 275 | end 276 | 277 | context "when neither has values" do 278 | let(:first) { Maybe.empty } 279 | let(:second) { Maybe.empty } 280 | 281 | it("returns true") { expect(subject).to be true } 282 | end 283 | end 284 | end 285 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | # frozen_string_literal: true 3 | 4 | require "sorbet-runtime" 5 | require "rspec/sorbet" 6 | require "rspec/json_expectations" 7 | 8 | require "pry" 9 | require_relative "../lib/resource_registry" 10 | -------------------------------------------------------------------------------- /spec/void_capability.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # typed: strict 3 | 4 | class VoidCapability < T::Struct 5 | extend T::Sig 6 | 7 | include ResourceRegistry::Capabilities::CapabilityConfig 8 | 9 | sig { override.returns(Symbol) } 10 | def self.key 11 | :void_capability 12 | end 13 | end 14 | --------------------------------------------------------------------------------