diff --git a/Gemfile b/Gemfile index 8ce5b9e6..f88019d9 100644 --- a/Gemfile +++ b/Gemfile @@ -27,3 +27,4 @@ group :test do gem "redis-client" gem "appraisal", "~> 2.5" end +gem "blueprinter" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..aedbc339 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,287 @@ +PATH + remote: . + specs: + rage-rb (1.22.0) + irb + logger + rack (< 4) + rack-test (~> 2.1) + rage-iodine (~> 5.2) + rake (>= 12.0) + thor (~> 1.0) + zeitwerk (~> 2.6) + +GEM + remote: https://rubygems.org/ + specs: + activesupport (8.1.2) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + json + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + appraisal (2.5.0) + bundler + rake + thor (>= 0.14.0) + ast (2.4.3) + base64 (0.3.0) + benchmark (0.5.0) + bigdecimal (4.0.1) + blueprinter (1.2.1) + concurrent-ruby (1.3.6) + connection_pool (2.5.5) + date (3.5.1) + diff-lcs (1.6.2) + domain_name (0.6.20240107) + drb (2.2.3) + erb (6.0.2) + event_emitter (0.2.6) + ffi (1.17.3) + ffi (1.17.3-aarch64-linux-gnu) + ffi (1.17.3-aarch64-linux-musl) + ffi (1.17.3-arm-linux-gnu) + ffi (1.17.3-arm-linux-musl) + ffi (1.17.3-arm64-darwin) + ffi (1.17.3-x86-linux-gnu) + ffi (1.17.3-x86-linux-musl) + ffi (1.17.3-x86_64-darwin) + ffi (1.17.3-x86_64-linux-gnu) + ffi (1.17.3-x86_64-linux-musl) + http (6.0.1) + http-cookie (~> 1.0) + llhttp (~> 0.6.1) + http-cookie (1.1.0) + domain_name (~> 0.5) + i18n (1.14.8) + concurrent-ruby (~> 1.0) + io-console (0.8.2) + irb (1.17.0) + pp (>= 0.6.0) + prism (>= 1.3.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + json (2.19.1) + language_server-protocol (3.17.0.5) + llhttp (0.6.1) + logger (1.7.0) + minitest (6.0.2) + drb (~> 2.0) + prism (~> 1.5) + mutex_m (0.3.0) + mysql2 (0.5.7) + bigdecimal + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.10.2) + ast (~> 2.4.1) + racc + pg (1.6.3) + pg (1.6.3-aarch64-linux) + pg (1.6.3-aarch64-linux-musl) + pg (1.6.3-arm64-darwin) + pg (1.6.3-x86_64-darwin) + pg (1.6.3-x86_64-linux) + pg (1.6.3-x86_64-linux-musl) + pp (0.6.3) + prettyprint + prettyprint (0.2.0) + prism (1.9.0) + psych (5.3.1) + date + stringio + racc (1.8.1) + rack (3.2.5) + rack-test (2.2.0) + rack (>= 1.3) + rage-iodine (5.2.0) + rainbow (3.1.1) + rake (13.3.1) + rbnacl (7.1.2) + ffi (~> 1) + rdoc (7.2.0) + erb + psych (>= 4.0.0) + tsort + redis-client (0.27.0) + connection_pool + regexp_parser (2.11.3) + reline (0.6.3) + io-console (~> 0.5) + rexml (3.4.4) + rspec (3.13.2) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.8) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.7) + rubocop (1.65.1) + json (~> 2.3) + language_server-protocol (>= 3.17.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.4, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.31.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.49.1) + parser (>= 3.3.7.2) + prism (~> 1.7) + ruby-progressbar (1.13.0) + securerandom (0.4.1) + stringio (3.2.0) + thor (1.5.0) + tsort (0.2.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (2.6.0) + uri (1.1.1) + websocket (1.2.11) + websocket-client-simple (0.9.0) + base64 + event_emitter + mutex_m + websocket + yard (0.9.38) + zeitwerk (2.7.5) + +PLATFORMS + aarch64-linux + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + ruby + x86-linux-gnu + x86-linux-musl + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + activesupport + appraisal (~> 2.5) + benchmark + bigdecimal + blueprinter + connection_pool (~> 2.0) + domain_name + http + mysql2 + ostruct + pg + prism + rage-rb! + rake (~> 13.0) + rbnacl + redis-client + rspec (~> 3.0) + rubocop (~> 1.65.0) + websocket-client-simple + yard + +CHECKSUMS + activesupport (8.1.2) sha256=88842578ccd0d40f658289b0e8c842acfe9af751afee2e0744a7873f50b6fdae + appraisal (2.5.0) sha256=36989221be127913b0dba8d114da2001e6b2dceea7bd4951200eaba764eed3ce + ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 + base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b + benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c + bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7 + blueprinter (1.2.1) sha256=11b534060b0b548f7c7d2084f891742fe12760d8df77cb9a1cffe502a9ceca10 + concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab + connection_pool (2.5.5) sha256=e54ff92855753df1fd7c59fa04a398833355f27dd14c074f8c83a05f72a716ad + date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0 + diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962 + domain_name (0.6.20240107) sha256=5f693b2215708476517479bf2b3802e49068ad82167bcd2286f899536a17d933 + drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373 + erb (6.0.2) sha256=9fe6264d44f79422c87490a1558479bd0e7dad4dd0e317656e67ea3077b5242b + event_emitter (0.2.6) sha256=c72697bd5cce9d36594be1972c17f1c9a573236f44303a4d1d548080364e1391 + ffi (1.17.3) sha256=0e9f39f7bb3934f77ad6feab49662be77e87eedcdeb2a3f5c0234c2938563d4c + ffi (1.17.3-aarch64-linux-gnu) sha256=28ad573df26560f0aedd8a90c3371279a0b2bd0b4e834b16a2baa10bd7a97068 + ffi (1.17.3-aarch64-linux-musl) sha256=020b33b76775b1abacc3b7d86b287cef3251f66d747092deec592c7f5df764b2 + ffi (1.17.3-arm-linux-gnu) sha256=5bd4cea83b68b5ec0037f99c57d5ce2dd5aa438f35decc5ef68a7d085c785668 + ffi (1.17.3-arm-linux-musl) sha256=0d7626bb96265f9af78afa33e267d71cfef9d9a8eb8f5525344f8da6c7d76053 + ffi (1.17.3-arm64-darwin) sha256=0c690555d4cee17a7f07c04d59df39b2fba74ec440b19da1f685c6579bb0717f + ffi (1.17.3-x86-linux-gnu) sha256=868a88fcaf5186c3a46b7c7c2b2c34550e1e61a405670ab23f5b6c9971529089 + ffi (1.17.3-x86-linux-musl) sha256=f0286aa6ef40605cf586e61406c446de34397b85dbb08cc99fdaddaef8343945 + ffi (1.17.3-x86_64-darwin) sha256=1f211811eb5cfaa25998322cdd92ab104bfbd26d1c4c08471599c511f2c00bb5 + ffi (1.17.3-x86_64-linux-gnu) sha256=3746b01f677aae7b16dc1acb7cb3cc17b3e35bdae7676a3f568153fb0e2c887f + ffi (1.17.3-x86_64-linux-musl) sha256=086b221c3a68320b7564066f46fed23449a44f7a1935f1fe5a245bd89d9aea56 + http (6.0.1) sha256=cff27eed3a0dcbd4f2eabab6807fde4f17597705cb7285bbdf0739da633700ec + http-cookie (1.1.0) sha256=38a5e60d1527eebc396831b8c4b9455440509881219273a6c99943d29eadbb19 + i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5 + io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc + irb (1.17.0) sha256=168c4ddb93d8a361a045c41d92b2952c7a118fa73f23fe14e55609eb7a863aae + json (2.19.1) sha256=dd94fdc59e48bff85913829a32350b3148156bc4fd2a95a2568a78b11344082d + language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc + llhttp (0.6.1) sha256=9da187ecf6407265465919cc0d691210ef79e38fa6e86e5e45593bdf25b50146 + logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 + minitest (6.0.2) sha256=db6e57956f6ecc6134683b4c87467d6dd792323c7f0eea7b93f66bd284adbc3d + mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751 + mysql2 (0.5.7) sha256=ba09ede515a0ae8a7192040a1b778c0fb0f025fa5877e9be895cd325fa5e9d7b + ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912 + parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 + parser (3.3.10.2) sha256=6f60c84aa4bdcedb6d1a2434b738fe8a8136807b6adc8f7f53b97da9bc4e9357 + pg (1.6.3) sha256=1388d0563e13d2758c1089e35e973a3249e955c659592d10e5b77c468f628a99 + pg (1.6.3-aarch64-linux) sha256=0698ad563e02383c27510b76bf7d4cd2de19cd1d16a5013f375dd473e4be72ea + pg (1.6.3-aarch64-linux-musl) sha256=06a75f4ea04b05140146f2a10550b8e0d9f006a79cdaf8b5b130cde40e3ecc2c + pg (1.6.3-arm64-darwin) sha256=7240330b572e6355d7c75a7de535edb5dfcbd6295d9c7777df4d9dddfb8c0e5f + pg (1.6.3-x86_64-darwin) sha256=ee2e04a17c0627225054ffeb43e31a95be9d7e93abda2737ea3ce4a62f2729d6 + pg (1.6.3-x86_64-linux) sha256=5d9e188c8f7a0295d162b7b88a768d8452a899977d44f3274d1946d67920ae8d + pg (1.6.3-x86_64-linux-musl) sha256=9c9c90d98c72f78eb04c0f55e9618fe55d1512128e411035fe229ff427864009 + pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6 + prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 + prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85 + psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974 + racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f + rack (3.2.5) sha256=4cbd0974c0b79f7a139b4812004a62e4c60b145cba76422e288ee670601ed6d3 + rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463 + rage-iodine (5.2.0) sha256=80cc85f0ad5065e3e4d7bfe8e4fcf48b2ad70125bd99503395f4ade17b5c76e5 + rage-rb (1.22.0) + rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a + rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c + rbnacl (7.1.2) sha256=c58433e93b390a7b8c03a9da9e54f2de67e6cc3d6731553265299c6aac889a60 + rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192 + redis-client (0.27.0) sha256=00e5918c1ba3fcd54b28e7be24b36fbf7b073e842c3c021ac072173c3eac42bd + regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4 + reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835 + rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 + rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587 + rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d + rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 + rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47 + rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c + rubocop (1.65.1) sha256=3a239b71fcfdeb32c654f4b48c2e6aeb4f77b128e348fa9442184f207e70718d + rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035 + ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 + securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 + stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1 + thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 + tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f + tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b + unicode-display_width (2.6.0) sha256=12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a + uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6 + websocket (1.2.11) sha256=b7e7a74e2410b5e85c25858b26b3322f29161e300935f70a0e0d3c35e0462737 + websocket-client-simple (0.9.0) sha256=f9a37c5e4922b35a711e21e6d73ed1e25892efa47d183203ab2f5beb4e563109 + yard (0.9.38) sha256=721fb82afb10532aa49860655f6cc2eaa7130889df291b052e1e6b268283010f + zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd + +BUNDLED WITH + 4.0.8 diff --git a/lib/rage/openapi/parsers/ext/blueprinter.rb b/lib/rage/openapi/parsers/ext/blueprinter.rb new file mode 100644 index 00000000..15891428 --- /dev/null +++ b/lib/rage/openapi/parsers/ext/blueprinter.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +class Rage::OpenAPI::Parsers::Ext::Blueprinter + attr_reader :namespace + + def initialize(namespace: Object, **) + @namespace = namespace + @parsing_stack = [] + end + + def known_definition?(str) + _, str = Rage::OpenAPI.__try_parse_collection(str) + defined?(Blueprinter::Base) && @namespace.const_get(str).ancestors.include?(Blueprinter::Base) + rescue NameError + false + end + + def parse(klass_str, view: :default) + __parse(klass_str, view:).build_schema + end + + def __parse_nested(klass_str) + return { "type" => "object" } if @parsing_stack.include?(klass_str) + __parse(klass_str).build_schema + end + + def __parse(klass_str, view: :default) + is_collection, klass_str = Rage::OpenAPI.__try_parse_collection(klass_str) + + klass = @namespace.const_get(klass_str) + source_path, _ = Object.const_source_location(klass.name) + ast = Prism.parse_file(source_path) + + @parsing_stack.push(klass_str) + visitor = Visitor.new(self, is_collection, klass_str, view) + ast.value.accept(visitor) + @parsing_stack.pop + + visitor + end + + class Visitor < Prism::Visitor + attr_accessor :schema + + def initialize(parser, is_collection, target_class_name, view) + @parser = parser + @is_collection = is_collection + @target_class_name = target_class_name + @view = view + @schema = {} + @current_segment = @schema + @current_class_name = nil + @current_view = :default + end + + def build_schema + result = { "type" => "object" } + result["properties"] = @schema if @schema.any? + + if @is_collection + result = { "type" => "array", "items" => result } + end + + result + end + + def visit_class_node(node) + previous_class = @current_class_name + @current_class_name = node.constant_path.slice + super + @current_class_name = previous_class + end + + def visit_call_node(node) + return super unless @current_class_name == @target_class_name + + case node.name + when :field, :identifier + # Only add field if we are in the requested view + return unless @current_view == :default || @current_view == @view + + first_arg = node.arguments&.arguments&.first + if first_arg.is_a?(Prism::SymbolNode) + field_name = first_arg.value.to_s + @current_segment[field_name] = { "type" => "string" } + end + + when :association + return unless @current_view == :default || @current_view == @view + + first_arg = node.arguments&.arguments&.first + if first_arg.is_a?(Prism::SymbolNode) + association_name = first_arg.value.to_s + + blueprint_klass_str = nil + node.arguments&.arguments&.each do |arg| + if arg.is_a?(Prism::KeywordHashNode) + arg.elements.each do |assoc| + if assoc.key.value == "blueprint" + blueprint_klass_str = assoc.value.slice + end + end + end + end + + @current_segment[association_name] = if blueprint_klass_str + @parser.__parse_nested(blueprint_klass_str) + else + { "type" => "object" } + end + end + + when :view + # Get the view name from the first argument + first_arg = node.arguments&.arguments&.first + if first_arg.is_a?(Prism::SymbolNode) + view_name = first_arg.value.to_sym + previous_view = @current_view + @current_view = view_name + # Visit the block contents + visit(node.block) + @current_view = previous_view + end + + else + super + end + end + end +end \ No newline at end of file diff --git a/spec/openapi/parsers/ext/blueprinter_spec.rb b/spec/openapi/parsers/ext/blueprinter_spec.rb new file mode 100644 index 00000000..49d58500 --- /dev/null +++ b/spec/openapi/parsers/ext/blueprinter_spec.rb @@ -0,0 +1,212 @@ +# frozen_string_literal: true + +require "prism" +require "blueprinter" +require "rage/openapi/parsers/ext/blueprinter" + +RSpec.describe Rage::OpenAPI::Parsers::Ext::Blueprinter do + include_context "mocked_classes" + + subject { described_class.new(**options).parse(resource, **parse_options) } + + let(:options) { {} } + let(:parse_options) { {} } + let(:resource) { "UserBlueprint" } + + context "with a single field" do + let_class("UserBlueprint", parent: Blueprinter::Base) do + <<~'RUBY' + field :name + RUBY + end + + it do + is_expected.to eq({ + "type" => "object", + "properties" => { + "name" => { "type" => "string" } + } + }) + end + end + + context "with multiple fields" do + let_class("UserBlueprint", parent: Blueprinter::Base) do + <<~'RUBY' + field :name + field :email + field :age + RUBY + end + + it do + is_expected.to eq({ + "type" => "object", + "properties" => { + "name" => { "type" => "string" }, + "email" => { "type" => "string" }, + "age" => { "type" => "string" } + } + }) + end + end + + context "with an identifier" do + let_class("UserBlueprint", parent: Blueprinter::Base) do + <<~'RUBY' + identifier :id + field :name + RUBY + end + + it do + is_expected.to eq({ + "type" => "object", + "properties" => { + "id" => { "type" => "string" }, + "name" => { "type" => "string" } + } + }) + end + end + + context "with an association" do + let_class("AddressBlueprint", parent: Blueprinter::Base) do + <<~'RUBY' + field :street + field :city + RUBY + end + + let_class("UserBlueprint", parent: Blueprinter::Base) do + <<~'RUBY' + field :name + association :address, blueprint: AddressBlueprint + RUBY + end + + it do + is_expected.to eq({ + "type" => "object", + "properties" => { + "name" => { "type" => "string" }, + "address" => { + "type" => "object", + "properties" => { + "street" => { "type" => "string" }, + "city" => { "type" => "string" } + } + } + } + }) + end + end + + context "with an association without blueprint" do + let_class("UserBlueprint", parent: Blueprinter::Base) do + <<~'RUBY' + field :name + association :metadata + RUBY + end + + it do + is_expected.to eq({ + "type" => "object", + "properties" => { + "name" => { "type" => "string" }, + "metadata" => { "type" => "object" } + } + }) + end + end + + context "with a default view" do + let_class("UserBlueprint", parent: Blueprinter::Base) do + <<~'RUBY' + field :name + field :email + view :extended do + field :phone + end + RUBY + end + + it "returns only default fields" do + is_expected.to eq({ + "type" => "object", + "properties" => { + "name" => { "type" => "string" }, + "email" => { "type" => "string" } + } + }) + end + end + + context "with an extended view" do + let_class("UserBlueprint", parent: Blueprinter::Base) do + <<~'RUBY' + field :name + field :email + view :extended do + field :phone + field :age + end + RUBY + end + + let(:parse_options) { { view: :extended } } + + it "returns default fields plus extended fields" do + is_expected.to eq({ + "type" => "object", + "properties" => { + "name" => { "type" => "string" }, + "email" => { "type" => "string" }, + "phone" => { "type" => "string" }, + "age" => { "type" => "string" } + } + }) + end + end + + context "with a collection" do + let_class("UserBlueprint", parent: Blueprinter::Base) do + <<~'RUBY' + field :name + field :email + RUBY + end + + let(:resource) { "[UserBlueprint]" } + + it "wraps schema in array" do + is_expected.to eq({ + "type" => "array", + "items" => { + "type" => "object", + "properties" => { + "name" => { "type" => "string" }, + "email" => { "type" => "string" } + } + } + }) + end + end + + context "with known_definition?" do + let_class("UserBlueprint", parent: Blueprinter::Base) do + <<~'RUBY' + field :name + RUBY + end + + it "returns true for a valid blueprint" do + expect(described_class.new.known_definition?("UserBlueprint")).to be(true) + end + + it "returns false for an unknown class" do + expect(described_class.new.known_definition?("UnknownClass")).to be(false) + end + end +end