From 11a223027ef476ed57b9f4c82af68bc2378424cb Mon Sep 17 00:00:00 2001 From: Teppei Shintani Date: Tue, 28 May 2024 23:51:42 +0900 Subject: [PATCH] =?UTF-8?q?Data.define=20=E3=81=AB=E5=AF=BE=E5=BF=9C?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=AF=E3=83=A9=E3=82=B9=E3=82=92=E8=87=AA?= =?UTF-8?q?=E5=8B=95=E7=94=9F=E6=88=90=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bundle exec ruby app.rb を実行すると、sig/generated/typed_data.rbs が生成されるようにした。 --- webapp/ruby/Rakefile | 10 +- webapp/ruby/app.rb | 161 +++++++++-------------- webapp/ruby/sig/generated/app.rbs | 86 ++---------- webapp/ruby/sig/generated/typed_data.rbs | 76 +++++++++++ 4 files changed, 151 insertions(+), 182 deletions(-) create mode 100644 webapp/ruby/sig/generated/typed_data.rbs diff --git a/webapp/ruby/Rakefile b/webapp/ruby/Rakefile index ffcb1ad..fc7c201 100644 --- a/webapp/ruby/Rakefile +++ b/webapp/ruby/Rakefile @@ -1,8 +1,8 @@ namespace :rbs do - task setup: %i[clean collection rbs_inline substract] + task setup: %i[clean collection rbs_inline typed_data substract] task :clean do - sh 'rm', '-rf', 'sig/generated/' + sh 'rm', '-rf', 'sig/generated/app.rbs' sh 'rm', '-rf', '.gem_rbs_collection/' end @@ -14,7 +14,11 @@ namespace :rbs do sh 'bundle', 'exec', 'rbs-inline', '--output', 'app.rb' end + task :typed_data do + sh 'bundle', 'exec', 'ruby', 'app.rb' + end + task :substract do - sh 'bundle', 'exec', 'rbs', 'subtract', '--write', 'sig/generated/app.rbs', 'sig/app.rbs' + sh 'bundle', 'exec', 'rbs', 'subtract', '--write', 'sig/generated/app.rbs', 'sig/generated/typed_data.rbs', 'sig/app.rbs' end end diff --git a/webapp/ruby/app.rb b/webapp/ruby/app.rb index b34fc2a..6fe4d67 100644 --- a/webapp/ruby/app.rb +++ b/webapp/ruby/app.rb @@ -18,6 +18,37 @@ def first! #:: Elem end end +class Data + # @rbs self.@classes: Hash[untyped, Hash[Symbol, String]] + @classes = {} + + #:: [KLASS < ::Data::_DataClass] (**untyped) ?{ (KLASS) [self: KLASS] -> void } -> KLASS + def self.typed_define(**attrs, &block) + k = define(*attrs.keys, &block) + @classes[k] = attrs + k + end + + def self.generate_rbs(class_name, attrs) + <<~RBS + class #{class_name} + extend Data::_DataClass + #{attrs.map { |k,v| "attr_reader #{k}: #{v}" }.join("\n ")} + def self.new: (*untyped) -> #{class_name} + | (**untyped) -> #{class_name} | ... + end + RBS + end + + at_exit do + body = '' + @classes.each do |klass, attrs| + body += generate_rbs(klass.name, attrs) + "\n" + end + File.write('sig/generated/typed_data.rbs', body) + end +end + module Isupipe class App < Sinatra::Base enable :logging @@ -25,7 +56,7 @@ class App < Sinatra::Base set :sessions, domain: 'u.isucon.dev', path: '/', expire_after: 1000*60 set :session_secret, ENV.fetch('ISUCON13_SESSION_SECRETKEY', 'isucon13_session_cookiestore_defaultsecret').unpack('H*')[0] - POWERDNS_SUBDOMAIN_ADDRESS = ENV.fetch('ISUCON13_POWERDNS_SUBDOMAIN_ADDRESS') + POWERDNS_SUBDOMAIN_ADDRESS = ENV.fetch('ISUCON13_POWERDNS_SUBDOMAIN_ADDRESS', '') DEFAULT_SESSION_ID_KEY = 'SESSIONID' DEFAULT_SESSION_EXPIRES_KEY = 'EXPIRES' @@ -281,29 +312,15 @@ def fill_user_response(tx, user_model) # livestream - # @rbs! - # class ReserveLivestreamRequest - # extend Data::_DataClass - # attr_reader tags: Array[Integer] - # attr_reader title: String - # attr_reader description: String - # attr_reader playlist_url: String - # attr_reader thumbnail_url: String - # attr_reader start_at: Integer - # attr_reader end_at: Integer - # def self.new: (*untyped) -> ReserveLivestreamRequest - # | (**untyped) -> ReserveLivestreamRequest | ... - # end - # @rbs skip - ReserveLivestreamRequest = Data.define( - :tags, - :title, - :description, - :playlist_url, - :thumbnail_url, - :start_at, - :end_at, + ReserveLivestreamRequest = Data.typed_define( + tags: 'Array[Integer]', + title: 'String', + description: 'String', + playlist_url: 'String', + thumbnail_url: 'String', + start_at: 'Integer', + end_at: 'Integer', ) # reserve livestream @@ -570,19 +587,10 @@ def fill_user_response(tx, user_model) json(ng_words) end - # @rbs! - # class PostLivecommentRequest - # extend Data::_DataClass - # attr_reader comment: String - # attr_reader tip: Integer - # def self.new: (*untyped) -> PostLivecommentRequest - # | (**untyped) -> PostLivecommentRequest | ... - # end - # @rbs skip - PostLivecommentRequest = Data.define( - :comment, - :tip, + PostLivecommentRequest = Data.typed_define( + comment: 'String', + tip: 'Integer', ) # ライブコメント投稿 @@ -687,16 +695,8 @@ def fill_user_response(tx, user_model) json(report) end - # @rbs! - # class ModerateRequest - # extend Data::_DataClass - # attr_reader ng_word: String - # def self.new: (*untyped) -> ModerateRequest - # | (**untyped) -> ModerateRequest | ... - # end - # @rbs skip - ModerateRequest = Data.define(:ng_word) #:: Class + ModerateRequest = Data.typed_define(ng_word: 'String') # 配信者によるモデレーション (NGワード登録) post '/api/livestream/:livestream_id/moderate' do @@ -772,16 +772,8 @@ def fill_user_response(tx, user_model) json(reactions) end - # @rbs! - # class PostReactionRequest - # extend Data::_DataClass - # attr_reader emoji_name: String - # def self.new: (*untyped) -> PostReactionRequest - # | (**untyped) -> PostReactionRequest | ... - # end - # @rbs skip - PostReactionRequest = Data.define(:emoji_name) + PostReactionRequest = Data.typed_define(emoji_name: 'String') post '/api/livestream/:livestream_id/reaction' do verify_user_session! @@ -838,16 +830,8 @@ def fill_user_response(tx, user_model) end end - # @rbs! - # class PostIconRequest - # extend Data::_DataClass - # attr_reader image: String - # def self.new: (*untyped) -> PostIconRequest - # | (**untyped) -> PostIconRequest | ... - # end - # @rbs skip - PostIconRequest = Data.define(:image) + PostIconRequest = Data.typed_define(image: 'String') post '/api/icon' do verify_user_session! @@ -899,26 +883,14 @@ def fill_user_response(tx, user_model) json(user) end - # @rbs! - # class PostUserRequest - # extend Data::_DataClass - # attr_reader name: String - # attr_reader display_name: String - # attr_reader description: String - # attr_reader password: String - # attr_reader theme: Hash[Symbol, untyped] - # def self.new: (*untyped) -> PostUserRequest - # | (**untyped) -> PostUserRequest | ... - # end - # @rbs skip - PostUserRequest = Data.define( - :name, - :display_name, - :description, + PostUserRequest = Data.typed_define( + name: 'String', + display_name: 'String', + description: 'String', # password is non-hashed password. - :password, - :theme, + password: 'String', + theme: 'Hash[Symbol, untyped]', ) # ユーザ登録API @@ -953,20 +925,11 @@ def fill_user_response(tx, user_model) json(user) end - # @rbs! - # class LoginRequest - # extend Data::_DataClass - # attr_reader username: String - # attr_reader password: String - # def self.new: (*untyped) -> LoginRequest - # | (**untyped) -> LoginRequest | ... - # end - # @rbs skip - LoginRequest = Data.define( - :username, + LoginRequest = Data.typed_define( + username: 'String', # password is non-hashed password. - :password, + password: 'String', ) # ユーザログインAPI @@ -1118,17 +1081,11 @@ def fill_user_response(tx, user_model) json(stats) end - # @rbs! - # class LivestreamRankingEntry - # extend Data::_DataClass - # attr_reader livestream_id: Integer - # attr_reader score: Integer - # def self.new: (*untyped) -> LivestreamRankingEntry - # | (**untyped) -> LivestreamRankingEntry | ... - # end - # @rbs skip - LivestreamRankingEntry = Data.define(:livestream_id, :score) + LivestreamRankingEntry = Data.typed_define( + livestream_id: 'Integer', + score: 'Integer', + ) # ライブ配信統計情報 get '/api/livestream/:livestream_id/statistics' do diff --git a/webapp/ruby/sig/generated/app.rbs b/webapp/ruby/sig/generated/app.rbs index b3e698f..4b86677 100644 --- a/webapp/ruby/sig/generated/app.rbs +++ b/webapp/ruby/sig/generated/app.rbs @@ -3,6 +3,15 @@ module Enumerable[unchecked out Elem] def first!: () -> Elem end +class Data + self.@classes: Hash[untyped, Hash[Symbol, String]] + + # :: [KLASS < ::Data::_DataClass] (**untyped) ?{ (KLASS) [self: KLASS] -> void } -> KLASS + def self.typed_define: [KLASS < ::Data::_DataClass] (**untyped) ?{ (KLASS) [self: KLASS] -> void } -> KLASS + + def self.generate_rbs: (untyped class_name, untyped attrs) -> untyped +end + module Isupipe class App < Sinatra::Base POWERDNS_SUBDOMAIN_ADDRESS: untyped @@ -71,87 +80,10 @@ module Isupipe # @rbs returns Hash[Symbol, untyped] def fill_user_response: (Mysql2::Client[Mysql2::ResultAsHash] tx, Hash[Symbol, Mysql2::row_value_type] user_model) -> Hash[Symbol, untyped] - class ReserveLivestreamRequest - extend Data::_DataClass - attr_reader tags: Array[Integer] - attr_reader title: String - attr_reader description: String - attr_reader playlist_url: String - attr_reader thumbnail_url: String - attr_reader start_at: Integer - attr_reader end_at: Integer - def self.new: (*untyped) -> ReserveLivestreamRequest - | (**untyped) -> ReserveLivestreamRequest - | ... - end - - class PostLivecommentRequest - extend Data::_DataClass - attr_reader comment: String - attr_reader tip: Integer - def self.new: (*untyped) -> PostLivecommentRequest - | (**untyped) -> PostLivecommentRequest - | ... - end - - class ModerateRequest - extend Data::_DataClass - attr_reader ng_word: String - def self.new: (*untyped) -> ModerateRequest - | (**untyped) -> ModerateRequest - | ... - end - - class PostReactionRequest - extend Data::_DataClass - attr_reader emoji_name: String - def self.new: (*untyped) -> PostReactionRequest - | (**untyped) -> PostReactionRequest - | ... - end - BCRYPT_DEFAULT_COST: ::Integer FALLBACK_IMAGE: ::String - class PostIconRequest - extend Data::_DataClass - attr_reader image: String - def self.new: (*untyped) -> PostIconRequest - | (**untyped) -> PostIconRequest - | ... - end - - class PostUserRequest - extend Data::_DataClass - attr_reader name: String - attr_reader display_name: String - attr_reader description: String - attr_reader password: String - attr_reader theme: Hash[Symbol, untyped] - def self.new: (*untyped) -> PostUserRequest - | (**untyped) -> PostUserRequest - | ... - end - - class LoginRequest - extend Data::_DataClass - attr_reader username: String - attr_reader password: String - def self.new: (*untyped) -> LoginRequest - | (**untyped) -> LoginRequest - | ... - end - UserRankingEntry: untyped - - class LivestreamRankingEntry - extend Data::_DataClass - attr_reader livestream_id: Integer - attr_reader score: Integer - def self.new: (*untyped) -> LivestreamRankingEntry - | (**untyped) -> LivestreamRankingEntry - | ... - end end end diff --git a/webapp/ruby/sig/generated/typed_data.rbs b/webapp/ruby/sig/generated/typed_data.rbs new file mode 100644 index 0000000..f89129a --- /dev/null +++ b/webapp/ruby/sig/generated/typed_data.rbs @@ -0,0 +1,76 @@ +class Isupipe::App::ReserveLivestreamRequest + extend Data::_DataClass + attr_reader tags: Array[Integer] + attr_reader title: String + attr_reader description: String + attr_reader playlist_url: String + attr_reader thumbnail_url: String + attr_reader start_at: Integer + attr_reader end_at: Integer + def self.new: (*untyped) -> Isupipe::App::ReserveLivestreamRequest + | (**untyped) -> Isupipe::App::ReserveLivestreamRequest + | ... +end + +class Isupipe::App::PostLivecommentRequest + extend Data::_DataClass + attr_reader comment: String + attr_reader tip: Integer + def self.new: (*untyped) -> Isupipe::App::PostLivecommentRequest + | (**untyped) -> Isupipe::App::PostLivecommentRequest + | ... +end + +class Isupipe::App::ModerateRequest + extend Data::_DataClass + attr_reader ng_word: String + def self.new: (*untyped) -> Isupipe::App::ModerateRequest + | (**untyped) -> Isupipe::App::ModerateRequest + | ... +end + +class Isupipe::App::PostReactionRequest + extend Data::_DataClass + attr_reader emoji_name: String + def self.new: (*untyped) -> Isupipe::App::PostReactionRequest + | (**untyped) -> Isupipe::App::PostReactionRequest + | ... +end + +class Isupipe::App::PostIconRequest + extend Data::_DataClass + attr_reader image: String + def self.new: (*untyped) -> Isupipe::App::PostIconRequest + | (**untyped) -> Isupipe::App::PostIconRequest + | ... +end + +class Isupipe::App::PostUserRequest + extend Data::_DataClass + attr_reader name: String + attr_reader display_name: String + attr_reader description: String + attr_reader password: String + attr_reader theme: Hash[Symbol, untyped] + def self.new: (*untyped) -> Isupipe::App::PostUserRequest + | (**untyped) -> Isupipe::App::PostUserRequest + | ... +end + +class Isupipe::App::LoginRequest + extend Data::_DataClass + attr_reader username: String + attr_reader password: String + def self.new: (*untyped) -> Isupipe::App::LoginRequest + | (**untyped) -> Isupipe::App::LoginRequest + | ... +end + +class Isupipe::App::LivestreamRankingEntry + extend Data::_DataClass + attr_reader livestream_id: Integer + attr_reader score: Integer + def self.new: (*untyped) -> Isupipe::App::LivestreamRankingEntry + | (**untyped) -> Isupipe::App::LivestreamRankingEntry + | ... +end