From 82067a2c1ea926d61d2fd3003ad206ca7d1d6f14 Mon Sep 17 00:00:00 2001 From: Lukas Alex Date: Mon, 15 May 2023 10:24:30 +0200 Subject: [PATCH 1/9] Adding Pry --- Gemfile | 1 + Gemfile.lock | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/Gemfile b/Gemfile index fa6f2c7..e1670d0 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,7 @@ source "https://rubygems.org" # Specify your gem's dependencies in scheemer.gemspec gemspec +gem "pry-nav" gem "rake", "~> 13.0" gem "rspec", "~> 3.0" gem "rubocop", "~> 1.21" diff --git a/Gemfile.lock b/Gemfile.lock index 2aabac0..8905728 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,6 +8,7 @@ GEM remote: https://rubygems.org/ specs: ast (2.4.2) + coderay (1.1.3) concurrent-ruby (1.2.0) diff-lcs (1.5.0) dry-configurable (1.0.1) @@ -37,9 +38,15 @@ GEM dry-logic (~> 1.4) zeitwerk (~> 2.6) json (2.6.3) + method_source (1.0.0) parallel (1.22.1) parser (3.2.1.0) ast (~> 2.4.1) + pry (0.14.2) + coderay (~> 1.1) + method_source (~> 1.0) + pry-nav (1.0.0) + pry (>= 0.9.10, < 0.15) rainbow (3.1.1) rake (13.0.6) regexp_parser (2.7.0) @@ -79,6 +86,7 @@ PLATFORMS x86_64-linux-musl DEPENDENCIES + pry-nav rake (~> 13.0) rspec (~> 3.0) rubocop (~> 1.21) From 5da58884907e2bc1eddd14070cf41551d011b67a Mon Sep 17 00:00:00 2001 From: Lukas Alex Date: Mon, 15 May 2023 10:29:46 +0200 Subject: [PATCH 2/9] test --- spec/scheemer/params_spec.rb | 4 ++-- spec/scheemer_spec.rb | 44 +++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/spec/scheemer/params_spec.rb b/spec/scheemer/params_spec.rb index cced0ea..eabcf84 100644 --- a/spec/scheemer/params_spec.rb +++ b/spec/scheemer/params_spec.rb @@ -24,7 +24,7 @@ end describe ".on_missing" do - context "with a single level key" do + context "with a shallow key" do let(:klass) do Class.new do extend Scheemer::Params::DSL @@ -35,7 +35,7 @@ subject(:record) { klass.new({ someValue: "testing" }) } - it "allows access to fields using underscored accessors" do + it "fills in the missing missing" do expect(record.content).to eql({ fall: "back" }) expect(record.someValue).to eql("testing") end diff --git a/spec/scheemer_spec.rb b/spec/scheemer_spec.rb index ec30500..fbec4b9 100644 --- a/spec/scheemer_spec.rb +++ b/spec/scheemer_spec.rb @@ -21,7 +21,9 @@ end end - subject(:record) { klass.new({ root: { someValue: "testing" } }) } + subject(:record) do + klass.new({ root: { someValue: "testing" } }) + end it "allows access to fields using underscored accessors" do expect(record.some_value).to eql("testing") @@ -58,5 +60,45 @@ it { expect { klass.new({}) }.to raise_error(NotImplementedError) } end + + context "when the extra fields are specified" do + let(:klass) do + Class.new do + extend Scheemer::DSL + + schema do + required(:item).hash do + required(:content).hash do + required(:name) + optional(:address) + end + end + end + end + end + let(:data) do + { + item: { + content: { + name: "John", + age: "9999", + address: "John's Street 69" + } + } + } + end + + subject(:record) do + klass.new({ item: { content: { "testing" } } }) + end + + it "it allows all fields through" do + require "pry" + binding.pry + result = schema.validate(data) + + expect(result.errors).to be_empty + end + end end end From 3970a2cabf2c22437c32066a5cc85d3b805aeca5 Mon Sep 17 00:00:00 2001 From: Lukas Alexandre Date: Mon, 15 May 2023 11:39:08 +0200 Subject: [PATCH 3/9] Allow undefined keys to be let through --- Gemfile.lock | 5 +++++ lib/scheemer/schema.rb | 5 +++++ scheemer.gemspec | 1 + spec/scheemer/schema_spec.rb | 2 +- spec/scheemer_spec.rb | 16 ++++++---------- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8905728..804f563 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,10 +3,13 @@ PATH specs: scheemer (3.0.0) dry-schema (~> 1.13) + json-schema (~> 4.0) GEM remote: https://rubygems.org/ specs: + addressable (2.8.4) + public_suffix (>= 2.0.2, < 6.0) ast (2.4.2) coderay (1.1.3) concurrent-ruby (1.2.0) @@ -38,6 +41,8 @@ GEM dry-logic (~> 1.4) zeitwerk (~> 2.6) json (2.6.3) + json-schema (4.0.0) + addressable (>= 2.8) method_source (1.0.0) parallel (1.22.1) parser (3.2.1.0) diff --git a/lib/scheemer/schema.rb b/lib/scheemer/schema.rb index 2cf2f91..a03b233 100644 --- a/lib/scheemer/schema.rb +++ b/lib/scheemer/schema.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "dry-schema" +require "json-schema" Dry::Schema.load_extensions(:hints, :json_schema) @@ -40,6 +41,8 @@ def check_schema_exists! def initialize(&) @definitions = ::Dry::Schema.Params do + config.validate_keys = false + instance_eval(&) end end @@ -49,6 +52,8 @@ def validate(params) end def validate!(params) + return params if JSON::Validator.validate(json_schema, params) + validate(params).tap do |result| next if result.success? diff --git a/scheemer.gemspec b/scheemer.gemspec index eceae48..f1ce19c 100644 --- a/scheemer.gemspec +++ b/scheemer.gemspec @@ -32,6 +32,7 @@ Gem::Specification.new do |spec| # Uncomment to register a new dependency of your gem spec.add_dependency "dry-schema", "~> 1.13" + spec.add_dependency "json-schema", "~> 4.0" # For more information and examples about making a new gem, check out our # guide at: https://bundler.io/guides/creating_gem.html diff --git a/spec/scheemer/schema_spec.rb b/spec/scheemer/schema_spec.rb index 86021a7..6d953b7 100644 --- a/spec/scheemer/schema_spec.rb +++ b/spec/scheemer/schema_spec.rb @@ -6,7 +6,7 @@ describe ".validate!" do subject(:schema) do described_class.new do - required(:test) + required(:test).filled(:string) end end diff --git a/spec/scheemer_spec.rb b/spec/scheemer_spec.rb index fbec4b9..145d447 100644 --- a/spec/scheemer_spec.rb +++ b/spec/scheemer_spec.rb @@ -69,8 +69,8 @@ schema do required(:item).hash do required(:content).hash do - required(:name) - optional(:address) + required(:name).filled(:string) + optional(:address).filled(:string) end end end @@ -88,16 +88,12 @@ } end - subject(:record) do - klass.new({ item: { content: { "testing" } } }) - end + subject(:record) { klass.new(data) } it "it allows all fields through" do - require "pry" - binding.pry - result = schema.validate(data) - - expect(result.errors).to be_empty + expect(record.content.dig(:name)).to eql "John" + expect(record.content.dig(:age)).to eql "9999" + expect(record.content.dig(:address)).to eql "John's Street 69" end end end From 080f445f1d0d969c7f4e00b23d69e432c33b4fcf Mon Sep 17 00:00:00 2001 From: Lukas Alexandre Date: Mon, 15 May 2023 14:13:41 +0200 Subject: [PATCH 4/9] Generates looser JsonSchema in order to export keys using `format?` --- lib/scheemer/schema.rb | 2 +- spec/scheemer/schema_spec.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/scheemer/schema.rb b/lib/scheemer/schema.rb index a03b233..ae48d91 100644 --- a/lib/scheemer/schema.rb +++ b/lib/scheemer/schema.rb @@ -62,7 +62,7 @@ def validate!(params) end def json_schema - @definitions.json_schema + @definitions.json_schema(loose: true) end end end diff --git a/spec/scheemer/schema_spec.rb b/spec/scheemer/schema_spec.rb index 6d953b7..a28eec1 100644 --- a/spec/scheemer/schema_spec.rb +++ b/spec/scheemer/schema_spec.rb @@ -24,4 +24,31 @@ end end end + + describe "#json_schema" do + context "when a key is using unsupported `format?`" do + subject(:schema) do + described_class.new do + required(:test).filled(format?: /asd/) + end + end + + it "generates the schema without it" do + expect(schema.json_schema).to eql( + { + :$schema => "http://json-schema.org/draft-06/schema#", + :properties => { + test: { + not: { + type: "null", + }, + }, + }, + :required => ["test"], + :type => "object", + } + ) + end + end + end end From 4ffa6c4630b407649a68fff56a8123332139f8fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CLukas?= <“lukas.alex@assemblyvoting.com”> Date: Mon, 15 May 2023 16:25:48 +0200 Subject: [PATCH 5/9] Don't skip format validation --- lib/scheemer/schema.rb | 7 +++++-- spec/scheemer/schema_spec.rb | 15 +++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/scheemer/schema.rb b/lib/scheemer/schema.rb index ae48d91..478f4fe 100644 --- a/lib/scheemer/schema.rb +++ b/lib/scheemer/schema.rb @@ -52,10 +52,13 @@ def validate(params) end def validate!(params) - return params if JSON::Validator.validate(json_schema, params) + valid_as_json = JSON::Validator.validate(json_schema, params) + valid_as_dry = validate(params) + + return params if valid_as_json && valid_as_dry validate(params).tap do |result| - next if result.success? + next if result.success? raise InvalidSchemaError, result.messages.to_h end diff --git a/spec/scheemer/schema_spec.rb b/spec/scheemer/schema_spec.rb index a28eec1..3c9ae15 100644 --- a/spec/scheemer/schema_spec.rb +++ b/spec/scheemer/schema_spec.rb @@ -29,23 +29,22 @@ context "when a key is using unsupported `format?`" do subject(:schema) do described_class.new do - required(:test).filled(format?: /asd/) + required(:test).filled(:str?, format?: /asd/) end end it "generates the schema without it" do expect(schema.json_schema).to eql( { - :$schema => "http://json-schema.org/draft-06/schema#", - :properties => { + "$schema": "http://json-schema.org/draft-06/schema#", + properties: { test: { - not: { - type: "null", - }, + minLength: 1, + type: "string", }, }, - :required => ["test"], - :type => "object", + required: ["test"], + type: "object", } ) end From 9e77547e9c19d999e6f209096e9b3f2cf60509e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CLukas?= <“lukas.alex@assemblyvoting.com”> Date: Mon, 15 May 2023 16:40:08 +0200 Subject: [PATCH 6/9] Fixerino --- lib/scheemer/schema.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/scheemer/schema.rb b/lib/scheemer/schema.rb index 478f4fe..6a2108a 100644 --- a/lib/scheemer/schema.rb +++ b/lib/scheemer/schema.rb @@ -55,13 +55,9 @@ def validate!(params) valid_as_json = JSON::Validator.validate(json_schema, params) valid_as_dry = validate(params) - return params if valid_as_json && valid_as_dry + return params if valid_as_json && valid_as_dry.success? - validate(params).tap do |result| - next if result.success? - - raise InvalidSchemaError, result.messages.to_h - end + raise InvalidSchemaError, valid_as_dry.messages.to_h end def json_schema From 04d5c7e0a12d9b38d3bb014c35105dec0393b402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CLukas?= <“lukas.alex@assemblyvoting.com”> Date: Mon, 15 May 2023 16:55:15 +0200 Subject: [PATCH 7/9] Accept string keys and transform them internally --- lib/scheemer/params.rb | 5 ++++- spec/scheemer/params_spec.rb | 24 +++++++++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/scheemer/params.rb b/lib/scheemer/params.rb index d548602..4182b69 100644 --- a/lib/scheemer/params.rb +++ b/lib/scheemer/params.rb @@ -28,7 +28,10 @@ def params_fallbacks module InstanceMethods def initialize(params, data = {}) - @params = Fallbacker.apply(params, self.class.params_fallbacks) + @params = Fallbacker.apply( + params.to_h.transform_keys(&:to_sym), + self.class.params_fallbacks + ) validate!(data.to_h) if respond_to?(:validate!) end diff --git a/spec/scheemer/params_spec.rb b/spec/scheemer/params_spec.rb index eabcf84..64a413f 100644 --- a/spec/scheemer/params_spec.rb +++ b/spec/scheemer/params_spec.rb @@ -11,14 +11,28 @@ end end - subject(:record) { klass.new({ someValue: "testing" }) } + context "when keys are symbols" do + subject(:record) { klass.new({ someValue: "testing" }) } + + it "allows access to fields using underscored accessors" do + expect(record.some_value).to eql("testing") + end - it "allows access to fields using underscored accessors" do - expect(record.some_value).to eql("testing") + it "allows access to fields using camelcase accessors" do + expect(record.someValue).to eql("testing") + end end - it "allows access to fields using camelcase accessors" do - expect(record.someValue).to eql("testing") + context "when keys are strings" do + subject(:record) { klass.new({ "someValue" => "testing" }) } + + it "allows access to fields using underscored accessors" do + expect(record.some_value).to eql("testing") + end + + it "allows access to fields using camelcase accessors" do + expect(record.someValue).to eql("testing") + end end end end From dc8324c7b20914cd425786eac7d753e2339fd751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CLukas?= <“lukas.alex@assemblyvoting.com”> Date: Mon, 15 May 2023 17:00:25 +0200 Subject: [PATCH 8/9] Now do it recursively --- lib/scheemer/params.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/scheemer/params.rb b/lib/scheemer/params.rb index 4182b69..f33ab03 100644 --- a/lib/scheemer/params.rb +++ b/lib/scheemer/params.rb @@ -29,7 +29,7 @@ def params_fallbacks module InstanceMethods def initialize(params, data = {}) @params = Fallbacker.apply( - params.to_h.transform_keys(&:to_sym), + params.to_h.transform_keys(recursive: true, &:to_sym), self.class.params_fallbacks ) From bc6135cd64ef57e9663a45f356da61d45c36dfa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CLukas?= <“lukas.alex@assemblyvoting.com”> Date: Mon, 15 May 2023 17:07:44 +0200 Subject: [PATCH 9/9] I give up. Bring in ActiveSupport --- Gemfile.lock | 11 +++++++++++ lib/scheemer/params.rb | 3 ++- scheemer.gemspec | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 99ec1c3..fa324ed 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,12 +2,18 @@ PATH remote: . specs: scheemer (3.0.0) + activesupport dry-schema (~> 1.13) json-schema (~> 4.0) GEM remote: https://rubygems.org/ specs: + activesupport (7.0.4.3) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) ast (2.4.2) @@ -40,10 +46,13 @@ GEM dry-inflector (~> 1.0) dry-logic (~> 1.4) zeitwerk (~> 2.6) + i18n (1.13.0) + concurrent-ruby (~> 1.0) json (2.6.3) json-schema (4.0.0) addressable (>= 2.8) method_source (1.0.0) + minitest (5.18.0) parallel (1.23.0) parser (3.2.2.1) ast (~> 2.4.1) @@ -85,6 +94,8 @@ GEM rubocop-rspec (2.12.1) rubocop (~> 1.31) ruby-progressbar (1.13.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) unicode-display_width (2.4.2) zeitwerk (2.6.8) diff --git a/lib/scheemer/params.rb b/lib/scheemer/params.rb index f33ab03..cf3e327 100644 --- a/lib/scheemer/params.rb +++ b/lib/scheemer/params.rb @@ -3,6 +3,7 @@ require_relative "./fallbacker" require_relative "./extensions/string" +require "active_support/core_ext/hash" module Scheemer # This handles the conversion from the HTTP linguo (camelCase) @@ -29,7 +30,7 @@ def params_fallbacks module InstanceMethods def initialize(params, data = {}) @params = Fallbacker.apply( - params.to_h.transform_keys(recursive: true, &:to_sym), + params.to_h.deep_symbolize_keys, self.class.params_fallbacks ) diff --git a/scheemer.gemspec b/scheemer.gemspec index f1ce19c..e90c088 100644 --- a/scheemer.gemspec +++ b/scheemer.gemspec @@ -33,6 +33,7 @@ Gem::Specification.new do |spec| # Uncomment to register a new dependency of your gem spec.add_dependency "dry-schema", "~> 1.13" spec.add_dependency "json-schema", "~> 4.0" + spec.add_dependency "activesupport" # For more information and examples about making a new gem, check out our # guide at: https://bundler.io/guides/creating_gem.html