hanami-ruby3 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +9 -1
- data/lib/hanami/hanami-ruby3.rb +2 -0
- data/lib/hanami/ruby3/version.rb +1 -1
- data/lib/hanami/ruby3.rb +4 -1
- data/lib/hanami/v1/README.md +11 -0
- data/lib/hanami/v1/action/base_params.rb +186 -0
- data/lib/hanami/v1/action/params.rb +248 -0
- data/lib/hanami/v1/validations/form.rb +72 -0
- data/lib/hanami/v1/validations/inline_predicate.rb +50 -0
- data/lib/hanami/v1/validations/namespace.rb +69 -0
- data/lib/hanami/v1/validations/predicates.rb +47 -0
- data/lib/hanami/v1/validations.rb +388 -0
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49e5e9e5b246dd74297750a1be4a0dff8f27f5bfd0cdac95dbeb3bbda1815c21
|
4
|
+
data.tar.gz: a36ed7e9847c8395e14a45d5a8214f9f0b890eeb261e88538f4d65077a94b86e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a22e0224fcd2d30defc87d8ff415aef7c6b7a8968354e139722f285019d5bfcf9948121e40ff2d615e27e07d20d8e536622b8306bb031eb3ebfe6f7657c566d3
|
7
|
+
data.tar.gz: 73264f585ebdac7972fd32c0fa7458b269650e715ae94a06035240ba443836088b0b6f08cb737aadfbb681cd5bc0c8220d5bc5c1978f337daa510ba64e1ea6c4
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Hanami Ruby 3.0
|
2
2
|
|
3
|
-
Monkey patches Hanami components to support Ruby 3.0
|
3
|
+
Monkey patches Hanami components to support Ruby 3.0. This includes
|
4
|
+
1. Copying Hanami 1.3.X files to support Ruby 3 syntax.
|
5
|
+
2. Copying Hanami 1.3.X files and getting rid of deprecated DRY libraries to allow upgrade of those gems.
|
4
6
|
|
5
7
|
## Overview
|
6
8
|
|
@@ -23,6 +25,12 @@ In your project, install overrides for the given classes
|
|
23
25
|
```ruby
|
24
26
|
# custom-hanami-utils is required to allow installing Hanami 1.3.5 with ruby 3
|
25
27
|
gem 'hanami-utils', github: 'swilgosz/hanami-utils', branch: '1.3.x-support-ruby-3.0'
|
28
|
+
|
29
|
+
# Deprecated DRY libraries are renamed to RDY - we install those so we can update our DRY libraries freely.
|
30
|
+
%w[core container configurable equalizer monads logic types struct validation].each do |name|
|
31
|
+
gem "rdy-#{name}", ascenda_private: "Kaligo/rdy", glob: "lib/rdy/#{name}/rdy-#{name}.gemspec"
|
32
|
+
end
|
33
|
+
|
26
34
|
gem 'hanami-ruby3'
|
27
35
|
```
|
28
36
|
|
data/lib/hanami/hanami-ruby3.rb
CHANGED
data/lib/hanami/ruby3/version.rb
CHANGED
data/lib/hanami/ruby3.rb
CHANGED
@@ -0,0 +1,11 @@
|
|
1
|
+
## Collection of overwritten Hanami 1.3.5 files
|
2
|
+
|
3
|
+
Ruby 3 folder contains Hanami 1.3.5 files tweaked to support Ruby 3 syntax.
|
4
|
+
This is different in the sense that we need to keep some Hanami 1.3 files in parallel
|
5
|
+
of updated versions of them.
|
6
|
+
|
7
|
+
We have renamed old DRY -> `RDY` and stored in `Kaligo/rdy` repository, so we can install newer versions of those libs in parallel. In order to do so, we need to kopy some of the Hanami files that depend on deprecated DRY gems and change them to use `RDY`
|
8
|
+
|
9
|
+
### Examples
|
10
|
+
|
11
|
+
- Hanami::Action::Params uses hanami-validations which uses old Dry-Validations with all the old dry-gems preventing us from gradual update. These files are updated to use RDY instead.
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'rack/request'
|
2
|
+
require 'hanami/utils/hash'
|
3
|
+
|
4
|
+
module Hanami
|
5
|
+
module V1
|
6
|
+
module Action
|
7
|
+
class BaseParams
|
8
|
+
# The key that returns raw input from the Rack env
|
9
|
+
#
|
10
|
+
# @since 0.7.0
|
11
|
+
# @api private
|
12
|
+
RACK_INPUT = 'rack.input'.freeze
|
13
|
+
|
14
|
+
# The key that returns router params from the Rack env
|
15
|
+
# This is a builtin integration for Hanami::Router
|
16
|
+
#
|
17
|
+
# @since 0.7.0
|
18
|
+
# @api private
|
19
|
+
ROUTER_PARAMS = 'router.params'.freeze
|
20
|
+
|
21
|
+
# The key that returns Rack session params from the Rack env
|
22
|
+
# Please note that this is used only when an action is unit tested.
|
23
|
+
#
|
24
|
+
# @since 1.0.0
|
25
|
+
# @api private
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# # action unit test
|
29
|
+
# action.call('rack.session' => { 'foo' => 'bar' })
|
30
|
+
# action.session[:foo] # => "bar"
|
31
|
+
RACK_SESSION = 'rack.session'.freeze
|
32
|
+
|
33
|
+
# HTTP request method for Rack env
|
34
|
+
#
|
35
|
+
# @since 1.1.1
|
36
|
+
# @api private
|
37
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
38
|
+
|
39
|
+
# Default HTTP request method for Rack env
|
40
|
+
#
|
41
|
+
# @since 1.1.1
|
42
|
+
# @api private
|
43
|
+
DEFAULT_REQUEST_METHOD = 'GET'.freeze
|
44
|
+
|
45
|
+
# @attr_reader env [Hash] the Rack env
|
46
|
+
#
|
47
|
+
# @since 0.7.0
|
48
|
+
# @api private
|
49
|
+
attr_reader :env
|
50
|
+
|
51
|
+
# @attr_reader raw [Hash] the raw params from the request
|
52
|
+
#
|
53
|
+
# @since 0.7.0
|
54
|
+
# @api private
|
55
|
+
attr_reader :raw
|
56
|
+
|
57
|
+
# Initialize the params and freeze them.
|
58
|
+
#
|
59
|
+
# @param env [Hash] a Rack env or an hash of params.
|
60
|
+
#
|
61
|
+
# @return [Params]
|
62
|
+
#
|
63
|
+
# @since 0.7.0
|
64
|
+
# @api private
|
65
|
+
def initialize(env)
|
66
|
+
@env = env
|
67
|
+
@raw = _extract_params
|
68
|
+
@params = Hanami::Utils::Hash.deep_symbolize(@raw)
|
69
|
+
freeze
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the object associated with the given key
|
73
|
+
#
|
74
|
+
# @param key [Symbol] the key
|
75
|
+
#
|
76
|
+
# @return [Object,nil] return the associated object, if found
|
77
|
+
#
|
78
|
+
# @since 0.7.0
|
79
|
+
def [](key)
|
80
|
+
@params[key]
|
81
|
+
end
|
82
|
+
|
83
|
+
# Get an attribute value associated with the given key.
|
84
|
+
# Nested attributes are reached by listing all the keys to get to the value.
|
85
|
+
#
|
86
|
+
# @param keys [Array<Symbol,Integer>] the key
|
87
|
+
#
|
88
|
+
# @return [Object,NilClass] return the associated value, if found
|
89
|
+
#
|
90
|
+
# @since 0.7.0
|
91
|
+
#
|
92
|
+
# @example
|
93
|
+
# require 'hanami/controller'
|
94
|
+
#
|
95
|
+
# module Deliveries
|
96
|
+
# class Create
|
97
|
+
# include Hanami::Action
|
98
|
+
#
|
99
|
+
# def call(params)
|
100
|
+
# params.get(:customer_name) # => "Luca"
|
101
|
+
# params.get(:uknown) # => nil
|
102
|
+
#
|
103
|
+
# params.get(:address, :city) # => "Rome"
|
104
|
+
# params.get(:address, :unknown) # => nil
|
105
|
+
#
|
106
|
+
# params.get(:tags, 0) # => "foo"
|
107
|
+
# params.get(:tags, 1) # => "bar"
|
108
|
+
# params.get(:tags, 999) # => nil
|
109
|
+
#
|
110
|
+
# params.get(nil) # => nil
|
111
|
+
# end
|
112
|
+
# end
|
113
|
+
# end
|
114
|
+
def get(*keys)
|
115
|
+
@params.dig(*keys)
|
116
|
+
end
|
117
|
+
|
118
|
+
# This is for compatibility with Hanami::Helpers::FormHelper::Values
|
119
|
+
#
|
120
|
+
# @api private
|
121
|
+
# @since 0.8.0
|
122
|
+
alias dig get
|
123
|
+
|
124
|
+
# Provide a common interface with Params
|
125
|
+
#
|
126
|
+
# @return [TrueClass] always returns true
|
127
|
+
#
|
128
|
+
# @since 0.7.0
|
129
|
+
#
|
130
|
+
# @see Hanami::Action::Params#valid?
|
131
|
+
def valid?
|
132
|
+
true
|
133
|
+
end
|
134
|
+
|
135
|
+
# Serialize params to Hash
|
136
|
+
#
|
137
|
+
# @return [::Hash]
|
138
|
+
#
|
139
|
+
# @since 0.7.0
|
140
|
+
def to_h
|
141
|
+
@params
|
142
|
+
end
|
143
|
+
alias_method :to_hash, :to_h
|
144
|
+
|
145
|
+
# Iterates through params
|
146
|
+
#
|
147
|
+
# @param blk [Proc]
|
148
|
+
#
|
149
|
+
# @since 0.7.1
|
150
|
+
def each(&blk)
|
151
|
+
to_h.each(&blk)
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
# @since 0.7.0
|
157
|
+
# @api private
|
158
|
+
def _extract_params
|
159
|
+
result = {}
|
160
|
+
|
161
|
+
if env.key?(RACK_INPUT)
|
162
|
+
result.merge! ::Rack::Request.new(env).params
|
163
|
+
result.merge! _router_params
|
164
|
+
else
|
165
|
+
result.merge! _router_params(env)
|
166
|
+
env[REQUEST_METHOD] ||= DEFAULT_REQUEST_METHOD
|
167
|
+
end
|
168
|
+
|
169
|
+
result
|
170
|
+
end
|
171
|
+
|
172
|
+
# @since 0.7.0
|
173
|
+
# @api private
|
174
|
+
def _router_params(fallback = {})
|
175
|
+
env.fetch(ROUTER_PARAMS) do
|
176
|
+
if session = fallback.delete(RACK_SESSION) # rubocop:disable Lint/AssignmentInCondition
|
177
|
+
fallback[RACK_SESSION] = Hanami::Utils::Hash.new(session).symbolize!.to_hash
|
178
|
+
end
|
179
|
+
|
180
|
+
fallback
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,248 @@
|
|
1
|
+
require_relative './base_params'
|
2
|
+
require_relative '../validations/form'
|
3
|
+
|
4
|
+
module Hanami
|
5
|
+
module V1
|
6
|
+
module Action
|
7
|
+
# A set of params requested by the client
|
8
|
+
#
|
9
|
+
# It's able to extract the relevant params from a Rack env of from an Hash.
|
10
|
+
#
|
11
|
+
# There are three scenarios:
|
12
|
+
# * When used with Hanami::Router: it contains only the params from the request
|
13
|
+
# * When used standalone: it contains all the Rack env
|
14
|
+
# * Default: it returns the given hash as it is. It's useful for testing purposes.
|
15
|
+
#
|
16
|
+
# @since 0.1.0
|
17
|
+
class Params < BaseParams
|
18
|
+
include Hanami::V1::Validations::Form
|
19
|
+
|
20
|
+
# Params errors
|
21
|
+
#
|
22
|
+
# @since 1.1.0
|
23
|
+
class Errors < SimpleDelegator
|
24
|
+
# @since 1.1.0
|
25
|
+
# @api private
|
26
|
+
def initialize(errors = {})
|
27
|
+
super(errors)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Add an error to the param validations
|
31
|
+
#
|
32
|
+
# This has a semantic similar to `Hash#dig` where you use a set of keys
|
33
|
+
# to get a nested value, here you use a set of keys to set a nested
|
34
|
+
# value.
|
35
|
+
#
|
36
|
+
# @param args [Array<Symbol, String>] an array of arguments: the last
|
37
|
+
# one is the message to add (String), while the beginning of the array
|
38
|
+
# is made of keys to reach the attribute.
|
39
|
+
#
|
40
|
+
# @raise [ArgumentError] when try to add a message for a key that is
|
41
|
+
# already filled with incompatible message type.
|
42
|
+
# This usually happens with nested attributes: if you have a `:book`
|
43
|
+
# schema and the input doesn't include data for `:book`, the messages
|
44
|
+
# will be `["is missing"]`. In that case you can't add an error for a
|
45
|
+
# key nested under `:book`.
|
46
|
+
#
|
47
|
+
# @since 1.1.0
|
48
|
+
#
|
49
|
+
# @example Basic usage
|
50
|
+
# require "hanami/controller"
|
51
|
+
#
|
52
|
+
# class MyAction
|
53
|
+
# include Hanami::Action
|
54
|
+
#
|
55
|
+
# params do
|
56
|
+
# required(:book).schema do
|
57
|
+
# required(:isbn).filled(:str?)
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# def call(params)
|
62
|
+
# # 1. Don't try to save the record if the params aren't valid
|
63
|
+
# return unless params.valid?
|
64
|
+
#
|
65
|
+
# BookRepository.new.create(params[:book])
|
66
|
+
# rescue Hanami::Model::UniqueConstraintViolationError
|
67
|
+
# # 2. Add an error in case the record wasn't unique
|
68
|
+
# params.errors.add(:book, :isbn, "is not unique")
|
69
|
+
# end
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# @example Invalid argument
|
73
|
+
# require "hanami/controller"
|
74
|
+
#
|
75
|
+
# class MyAction
|
76
|
+
# include Hanami::Action
|
77
|
+
#
|
78
|
+
# params do
|
79
|
+
# required(:book).schema do
|
80
|
+
# required(:title).filled(:str?)
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# def call(params)
|
85
|
+
# puts params.to_h # => {}
|
86
|
+
# puts params.valid? # => false
|
87
|
+
# puts params.error_messages # => ["Book is missing"]
|
88
|
+
# puts params.errors # => {:book=>["is missing"]}
|
89
|
+
#
|
90
|
+
# params.errors.add(:book, :isbn, "is not unique") # => ArgumentError
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
def add(*args)
|
94
|
+
*keys, key, error = args
|
95
|
+
_nested_attribute(keys, key) << error
|
96
|
+
rescue TypeError
|
97
|
+
raise ArgumentError.new("Can't add #{args.map(&:inspect).join(', ')} to #{inspect}")
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
# @since 1.1.0
|
103
|
+
# @api private
|
104
|
+
def _nested_attribute(keys, key)
|
105
|
+
if keys.empty?
|
106
|
+
self
|
107
|
+
else
|
108
|
+
keys.inject(self) { |result, k| result[k] ||= {} }
|
109
|
+
dig(*keys)
|
110
|
+
end[key] ||= []
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# This is a Hanami::Validations extension point
|
115
|
+
#
|
116
|
+
# @since 0.7.0
|
117
|
+
# @api private
|
118
|
+
def self._base_rules
|
119
|
+
lambda do
|
120
|
+
optional(:_csrf_token).filled(:str?)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Define params validations
|
125
|
+
#
|
126
|
+
# @param blk [Proc] the validations definitions
|
127
|
+
#
|
128
|
+
# @since 0.7.0
|
129
|
+
#
|
130
|
+
# @see https://guides.hanamirb.org/validations/overview
|
131
|
+
#
|
132
|
+
# @example
|
133
|
+
# class Signup
|
134
|
+
# MEGABYTE = 1024 ** 2
|
135
|
+
# include Hanami::Action
|
136
|
+
#
|
137
|
+
# params do
|
138
|
+
# required(:first_name).filled(:str?)
|
139
|
+
# required(:last_name).filled(:str?)
|
140
|
+
# required(:email).filled?(:str?, format?: /\A.+@.+\z/)
|
141
|
+
# required(:password).filled(:str?).confirmation
|
142
|
+
# required(:terms_of_service).filled(:bool?)
|
143
|
+
# required(:age).filled(:int?, included_in?: 18..99)
|
144
|
+
# optional(:avatar).filled(size?: 1..(MEGABYTE * 3))
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
# def call(params)
|
148
|
+
# halt 400 unless params.valid?
|
149
|
+
# # ...
|
150
|
+
# end
|
151
|
+
# end
|
152
|
+
def self.params(&blk)
|
153
|
+
validations(&blk || ->() {})
|
154
|
+
end
|
155
|
+
|
156
|
+
# Initialize the params and freeze them.
|
157
|
+
#
|
158
|
+
# @param env [Hash] a Rack env or an hash of params.
|
159
|
+
#
|
160
|
+
# @return [Params]
|
161
|
+
#
|
162
|
+
# @since 0.1.0
|
163
|
+
# @api private
|
164
|
+
def initialize(env)
|
165
|
+
@env = env
|
166
|
+
super(_extract_params)
|
167
|
+
@result = validate
|
168
|
+
@params = _params
|
169
|
+
@errors = Errors.new(@result.messages)
|
170
|
+
freeze
|
171
|
+
end
|
172
|
+
|
173
|
+
# Returns raw params from Rack env
|
174
|
+
#
|
175
|
+
# @return [Hash]
|
176
|
+
#
|
177
|
+
# @since 0.3.2
|
178
|
+
def raw
|
179
|
+
@input
|
180
|
+
end
|
181
|
+
|
182
|
+
# Returns structured error messages
|
183
|
+
#
|
184
|
+
# @return [Hash]
|
185
|
+
#
|
186
|
+
# @since 0.7.0
|
187
|
+
#
|
188
|
+
# @example
|
189
|
+
# params.errors
|
190
|
+
# # => {:email=>["is missing", "is in invalid format"], :name=>["is missing"], :tos=>["is missing"], :age=>["is missing"], :address=>["is missing"]}
|
191
|
+
attr_reader :errors
|
192
|
+
|
193
|
+
# Returns flat collection of full error messages
|
194
|
+
#
|
195
|
+
# @return [Array]
|
196
|
+
#
|
197
|
+
# @since 0.7.0
|
198
|
+
#
|
199
|
+
# @example
|
200
|
+
# params.error_messages
|
201
|
+
# # => ["Email is missing", "Email is in invalid format", "Name is missing", "Tos is missing", "Age is missing", "Address is missing"]
|
202
|
+
def error_messages(error_set = errors)
|
203
|
+
error_set.each_with_object([]) do |(key, messages), result|
|
204
|
+
k = Utils::String.titleize(key)
|
205
|
+
|
206
|
+
_messages = if messages.is_a?(Hash)
|
207
|
+
error_messages(messages)
|
208
|
+
else
|
209
|
+
messages.map { |message| "#{k} #{message}" }
|
210
|
+
end
|
211
|
+
|
212
|
+
result.concat(_messages)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Returns true if no validation errors are found,
|
217
|
+
# false otherwise.
|
218
|
+
#
|
219
|
+
# @return [TrueClass, FalseClass]
|
220
|
+
#
|
221
|
+
# @since 0.7.0
|
222
|
+
#
|
223
|
+
# @example
|
224
|
+
# params.valid? # => true
|
225
|
+
def valid?
|
226
|
+
errors.empty?
|
227
|
+
end
|
228
|
+
|
229
|
+
# Serialize params to Hash
|
230
|
+
#
|
231
|
+
# @return [::Hash]
|
232
|
+
#
|
233
|
+
# @since 0.3.0
|
234
|
+
def to_h
|
235
|
+
@params
|
236
|
+
end
|
237
|
+
alias_method :to_hash, :to_h
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
# @api private
|
242
|
+
def _params
|
243
|
+
_router_params.merge(@result.output)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../validations"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
module V1
|
7
|
+
module Validations
|
8
|
+
# Validations mixin for forms/HTTP params.
|
9
|
+
#
|
10
|
+
# This must be used when the input comes from a browser or an HTTP endpoint.
|
11
|
+
# It knows how to deal with common data types, and common edge cases like blank strings.
|
12
|
+
#
|
13
|
+
# @since 0.6.0
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# require 'hanami/validations/form'
|
17
|
+
#
|
18
|
+
# class Signup
|
19
|
+
# include Hanami::Validations::Form
|
20
|
+
#
|
21
|
+
# validations do
|
22
|
+
# required(:name).filled(:str?)
|
23
|
+
# optional(:location).filled(:str?)
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# result = Signup.new('location' => 'Rome').validate
|
28
|
+
# result.success? # => false
|
29
|
+
#
|
30
|
+
# result = Signup.new('name' => 'Luca').validate
|
31
|
+
# result.success? # => true
|
32
|
+
#
|
33
|
+
# # it works with symbol keys too
|
34
|
+
# result = Signup.new(location: 'Rome').validate
|
35
|
+
# result.success? # => false
|
36
|
+
#
|
37
|
+
# result = Signup.new(name: 'Luca').validate
|
38
|
+
# result.success? # => true
|
39
|
+
#
|
40
|
+
# result = Signup.new(name: 'Luca', location: 'Rome').validate
|
41
|
+
# result.success? # => true
|
42
|
+
module Form
|
43
|
+
# Override Ruby's hook for modules.
|
44
|
+
#
|
45
|
+
# @param base [Class] the target action
|
46
|
+
#
|
47
|
+
# @since 0.6.0
|
48
|
+
# @api private
|
49
|
+
#
|
50
|
+
# @see http://www.ruby-doc.org/core/Module.html#method-i-included
|
51
|
+
def self.included(base)
|
52
|
+
base.class_eval do
|
53
|
+
include Hanami::V1::Validations
|
54
|
+
extend ClassMethods
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# @since 0.6.0
|
59
|
+
# @api private
|
60
|
+
module ClassMethods
|
61
|
+
private
|
62
|
+
|
63
|
+
# @since 0.6.0
|
64
|
+
# @api private
|
65
|
+
def _schema_type
|
66
|
+
:Form
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module V1
|
5
|
+
module Validations
|
6
|
+
# Inline predicate
|
7
|
+
#
|
8
|
+
# @since 0.6.0
|
9
|
+
# @api private
|
10
|
+
class InlinePredicate
|
11
|
+
# @since 0.6.0
|
12
|
+
# @api private
|
13
|
+
attr_reader :name
|
14
|
+
|
15
|
+
# @since 0.6.0
|
16
|
+
# @api private
|
17
|
+
attr_reader :message
|
18
|
+
|
19
|
+
# Return a new instance.
|
20
|
+
#
|
21
|
+
# @param name [Symbol] inline predicate name
|
22
|
+
# @param message [String] optional failure message
|
23
|
+
# @param blk [Proc] predicate implementation
|
24
|
+
#
|
25
|
+
# @return [Hanami::Validations::InlinePredicate] a new instance
|
26
|
+
#
|
27
|
+
# @since 0.6.0
|
28
|
+
# @api private
|
29
|
+
def initialize(name, message, &blk)
|
30
|
+
@name = name
|
31
|
+
@message = message
|
32
|
+
@blk = blk
|
33
|
+
end
|
34
|
+
|
35
|
+
# @since 0.6.0
|
36
|
+
# @api private
|
37
|
+
def to_proc
|
38
|
+
@blk
|
39
|
+
end
|
40
|
+
|
41
|
+
# @since 0.6.0
|
42
|
+
# @api private
|
43
|
+
def ==(other)
|
44
|
+
self.class == other.class &&
|
45
|
+
name == other.name
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hanami/utils/string"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
module V1
|
7
|
+
module Validations
|
8
|
+
# Validations message namespace.
|
9
|
+
#
|
10
|
+
# For a given `FooValidator` class, it will look for I18n messages within
|
11
|
+
# the `foo` group.
|
12
|
+
#
|
13
|
+
# @since 0.6.0
|
14
|
+
# @api private
|
15
|
+
class Namespace
|
16
|
+
# @since 0.6.0
|
17
|
+
# @api private
|
18
|
+
SUFFIX = "Validator"
|
19
|
+
|
20
|
+
# @since 0.6.0
|
21
|
+
# @api private
|
22
|
+
SUFFIX_REPLACEMENT = ""
|
23
|
+
|
24
|
+
# @since 0.6.0
|
25
|
+
# @api private
|
26
|
+
RUBY_NAMESPACE_SEPARATOR = "/"
|
27
|
+
|
28
|
+
# @since 0.6.0
|
29
|
+
# @api private
|
30
|
+
RUBY_NAMESPACE_REPLACEMENT = "."
|
31
|
+
|
32
|
+
# @since 0.6.0
|
33
|
+
# @api private
|
34
|
+
def self.new(name, klass)
|
35
|
+
result = name || klass.name
|
36
|
+
return nil if result.nil?
|
37
|
+
|
38
|
+
super(result)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @since 0.6.0
|
42
|
+
# @api private
|
43
|
+
def initialize(name)
|
44
|
+
@name = name
|
45
|
+
end
|
46
|
+
|
47
|
+
# @since 0.6.0
|
48
|
+
# @api private
|
49
|
+
def to_s
|
50
|
+
underscored_name.gsub(RUBY_NAMESPACE_SEPARATOR, RUBY_NAMESPACE_REPLACEMENT)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# @since 0.6.0
|
56
|
+
# @api private
|
57
|
+
def underscored_name
|
58
|
+
Hanami::Utils::String.underscore(name_without_suffix)
|
59
|
+
end
|
60
|
+
|
61
|
+
# @since 0.6.0
|
62
|
+
# @api private
|
63
|
+
def name_without_suffix
|
64
|
+
@name.sub(SUFFIX, SUFFIX_REPLACEMENT)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rdy/logic/predicates"
|
4
|
+
require "hanami/utils/class_attribute"
|
5
|
+
|
6
|
+
module Hanami
|
7
|
+
module Validations
|
8
|
+
# Mixin to include when defining shared predicates
|
9
|
+
#
|
10
|
+
# @since 0.6.0
|
11
|
+
#
|
12
|
+
# @see Hanami::Validations::ClassMethods#predicates
|
13
|
+
#
|
14
|
+
# @example Inline Predicate
|
15
|
+
# require 'hanami/validations'
|
16
|
+
#
|
17
|
+
# module MySharedPredicates
|
18
|
+
# include Hanami::Validations::Predicates
|
19
|
+
#
|
20
|
+
# predicate :foo? do |actual|
|
21
|
+
# actual == 'foo'
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# class MyValidator
|
26
|
+
# include Hanami::Validations
|
27
|
+
# predicates MySharedPredicates
|
28
|
+
#
|
29
|
+
# validations do
|
30
|
+
# required(:name).filled(:foo?)
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
module Predicates
|
34
|
+
# @since 0.6.0
|
35
|
+
# @api private
|
36
|
+
def self.included(base)
|
37
|
+
base.class_eval do
|
38
|
+
include Rdy::Logic::Predicates
|
39
|
+
include Hanami::Utils::ClassAttribute
|
40
|
+
|
41
|
+
class_attribute :messages
|
42
|
+
class_attribute :messages_path
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,388 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rdy-validation"
|
4
|
+
require "hanami/utils/class_attribute"
|
5
|
+
require_relative "./validations/namespace"
|
6
|
+
require_relative "./validations/predicates"
|
7
|
+
require_relative "./validations/inline_predicate"
|
8
|
+
require "set"
|
9
|
+
|
10
|
+
Rdy::Validation::Messages::Namespaced.configure do |config|
|
11
|
+
# rubocop:disable Lint/NestedPercentLiteral
|
12
|
+
#
|
13
|
+
# This is probably a false positive.
|
14
|
+
# See: https://github.com/bbatsov/rubocop/issues/5314
|
15
|
+
config.lookup_paths = config.lookup_paths + %w[
|
16
|
+
%<root>s.%<rule>s.%<predicate>s
|
17
|
+
].freeze
|
18
|
+
# rubocop:enable Lint/NestedPercentLiteral
|
19
|
+
end
|
20
|
+
|
21
|
+
# @since 0.1.0
|
22
|
+
module Hanami
|
23
|
+
module V1
|
24
|
+
# Hanami::Validations is a set of lightweight validations for Ruby objects.
|
25
|
+
#
|
26
|
+
# @since 0.1.0
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# require 'hanami/validations'
|
30
|
+
#
|
31
|
+
# class Signup
|
32
|
+
# include Hanami::Validations
|
33
|
+
#
|
34
|
+
# validations do
|
35
|
+
# # ...
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
module Validations
|
39
|
+
# @since 0.6.0
|
40
|
+
# @api private
|
41
|
+
DEFAULT_MESSAGES_ENGINE = :yaml
|
42
|
+
|
43
|
+
# Override Ruby's hook for modules.
|
44
|
+
#
|
45
|
+
# @param base [Class] the target action
|
46
|
+
#
|
47
|
+
# @since 0.1.0
|
48
|
+
# @api private
|
49
|
+
#
|
50
|
+
# @see http://www.ruby-doc.org/core/Module.html#method-i-included
|
51
|
+
def self.included(base)
|
52
|
+
base.class_eval do
|
53
|
+
extend ClassMethods
|
54
|
+
|
55
|
+
include Hanami::Utils::ClassAttribute
|
56
|
+
class_attribute :schema
|
57
|
+
class_attribute :_messages
|
58
|
+
class_attribute :_messages_path
|
59
|
+
class_attribute :_namespace
|
60
|
+
class_attribute :_predicates_module
|
61
|
+
|
62
|
+
class_attribute :_predicates
|
63
|
+
self._predicates = Set.new
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Validations DSL
|
68
|
+
#
|
69
|
+
# @since 0.1.0
|
70
|
+
module ClassMethods
|
71
|
+
# Define validation rules from the given block.
|
72
|
+
#
|
73
|
+
# @param blk [Proc] validation rules
|
74
|
+
#
|
75
|
+
# @since 0.6.0
|
76
|
+
#
|
77
|
+
# @see https://guides.hanamirb.org/validations/overview
|
78
|
+
#
|
79
|
+
# @example Basic Example
|
80
|
+
# require 'hanami/validations'
|
81
|
+
#
|
82
|
+
# class Signup
|
83
|
+
# include Hanami::Validations
|
84
|
+
#
|
85
|
+
# validations do
|
86
|
+
# required(:name).filled
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# result = Signup.new(name: "Luca").validate
|
91
|
+
#
|
92
|
+
# result.success? # => true
|
93
|
+
# result.messages # => []
|
94
|
+
# result.output # => {:name=>""}
|
95
|
+
#
|
96
|
+
# result = Signup.new(name: "").validate
|
97
|
+
#
|
98
|
+
# result.success? # => false
|
99
|
+
# result.messages # => {:name=>["must be filled"]}
|
100
|
+
# result.output # => {:name=>""}
|
101
|
+
def validations(&blk)
|
102
|
+
schema_predicates = __predicates
|
103
|
+
|
104
|
+
base = _build(predicates: schema_predicates, &_base_rules)
|
105
|
+
schema = _build(predicates: schema_predicates, rules: base.rules, &blk)
|
106
|
+
schema.configure(&_schema_config)
|
107
|
+
schema.configure(&_schema_predicates)
|
108
|
+
schema.extend(__messages) unless _predicates.empty?
|
109
|
+
|
110
|
+
self.schema = schema.new
|
111
|
+
end
|
112
|
+
|
113
|
+
# Define an inline predicate
|
114
|
+
#
|
115
|
+
# @param name [Symbol] inline predicate name
|
116
|
+
# @param message [String] optional error message
|
117
|
+
# @param blk [Proc] predicate implementation
|
118
|
+
#
|
119
|
+
# @return nil
|
120
|
+
#
|
121
|
+
# @since 0.6.0
|
122
|
+
#
|
123
|
+
# @example Without Custom Message
|
124
|
+
# require 'hanami/validations'
|
125
|
+
#
|
126
|
+
# class Signup
|
127
|
+
# include Hanami::Validations
|
128
|
+
#
|
129
|
+
# predicate :foo? do |actual|
|
130
|
+
# actual == 'foo'
|
131
|
+
# end
|
132
|
+
#
|
133
|
+
# validations do
|
134
|
+
# required(:name).filled(:foo?)
|
135
|
+
# end
|
136
|
+
# end
|
137
|
+
#
|
138
|
+
# result = Signup.new(name: nil).call
|
139
|
+
# result.messages # => { :name => ['is invalid'] }
|
140
|
+
#
|
141
|
+
# @example With Custom Message
|
142
|
+
# require 'hanami/validations'
|
143
|
+
#
|
144
|
+
# class Signup
|
145
|
+
# include Hanami::Validations
|
146
|
+
#
|
147
|
+
# predicate :foo?, message: 'must be foo' do |actual|
|
148
|
+
# actual == 'foo'
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# validations do
|
152
|
+
# required(:name).filled(:foo?)
|
153
|
+
# end
|
154
|
+
# end
|
155
|
+
#
|
156
|
+
# result = Signup.new(name: nil).call
|
157
|
+
# result.messages # => { :name => ['must be foo'] }
|
158
|
+
def predicate(name, message: "is invalid", &blk)
|
159
|
+
_predicates << InlinePredicate.new(name, message, &blk)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Assign a set of shared predicates wrapped in a module
|
163
|
+
#
|
164
|
+
# @param mod [Module] a module with shared predicates
|
165
|
+
#
|
166
|
+
# @since 0.6.0
|
167
|
+
#
|
168
|
+
# @see Hanami::Validations::Predicates
|
169
|
+
#
|
170
|
+
# @example
|
171
|
+
# require 'hanami/validations'
|
172
|
+
#
|
173
|
+
# module MySharedPredicates
|
174
|
+
# include Hanami::Validations::Predicates
|
175
|
+
#
|
176
|
+
# predicate :foo? fo |actual|
|
177
|
+
# actual == 'foo'
|
178
|
+
# end
|
179
|
+
# end
|
180
|
+
#
|
181
|
+
# class MyValidator
|
182
|
+
# include Hanami::Validations
|
183
|
+
# predicates MySharedPredicates
|
184
|
+
#
|
185
|
+
# validations do
|
186
|
+
# required(:name).filled(:foo?)
|
187
|
+
# end
|
188
|
+
# end
|
189
|
+
def predicates(mod)
|
190
|
+
self._predicates_module = mod
|
191
|
+
end
|
192
|
+
|
193
|
+
# Define the type of engine for error messages.
|
194
|
+
#
|
195
|
+
# Accepted values are `:yaml` (default), `:i18n`.
|
196
|
+
#
|
197
|
+
# @param type [Symbol] the preferred engine
|
198
|
+
#
|
199
|
+
# @since 0.6.0
|
200
|
+
#
|
201
|
+
# @example
|
202
|
+
# require 'hanami/validations'
|
203
|
+
#
|
204
|
+
# class Signup
|
205
|
+
# include Hanami::Validations
|
206
|
+
#
|
207
|
+
# messages :i18n
|
208
|
+
# end
|
209
|
+
def messages(type)
|
210
|
+
self._messages = type
|
211
|
+
end
|
212
|
+
|
213
|
+
# Define the path where to find translation file
|
214
|
+
#
|
215
|
+
# @param path [String] path to translation file
|
216
|
+
#
|
217
|
+
# @since 0.6.0
|
218
|
+
#
|
219
|
+
# @example
|
220
|
+
# require 'hanami/validations'
|
221
|
+
#
|
222
|
+
# class Signup
|
223
|
+
# include Hanami::Validations
|
224
|
+
#
|
225
|
+
# messages_path 'config/messages.yml'
|
226
|
+
# end
|
227
|
+
def messages_path(path)
|
228
|
+
self._messages_path = path
|
229
|
+
end
|
230
|
+
|
231
|
+
# Namespace for error messages.
|
232
|
+
#
|
233
|
+
# @param name [String] namespace
|
234
|
+
#
|
235
|
+
# @since 0.6.0
|
236
|
+
#
|
237
|
+
# @example
|
238
|
+
# require 'hanami/validations'
|
239
|
+
#
|
240
|
+
# module MyApp
|
241
|
+
# module Validators
|
242
|
+
# class Signup
|
243
|
+
# include Hanami::Validations
|
244
|
+
#
|
245
|
+
# namespace 'signup'
|
246
|
+
# end
|
247
|
+
# end
|
248
|
+
# end
|
249
|
+
#
|
250
|
+
# # Instead of looking for error messages under the `my_app.validator.signup`
|
251
|
+
# # namespace, it will look just for `signup`.
|
252
|
+
# #
|
253
|
+
# # This helps to simplify YAML files where are stored error messages
|
254
|
+
def namespace(name = nil)
|
255
|
+
if name.nil?
|
256
|
+
Namespace.new(_namespace, self)
|
257
|
+
else
|
258
|
+
self._namespace = name.to_s
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
private
|
263
|
+
|
264
|
+
# @since 0.6.0
|
265
|
+
# @api private
|
266
|
+
def _build(options = {}, &blk)
|
267
|
+
options = {build: false}.merge(options)
|
268
|
+
Rdy::Validation.__send__(_schema_type, options, &blk)
|
269
|
+
end
|
270
|
+
|
271
|
+
# @since 0.6.0
|
272
|
+
# @api private
|
273
|
+
def _schema_type
|
274
|
+
:Schema
|
275
|
+
end
|
276
|
+
|
277
|
+
# @since 0.6.0
|
278
|
+
# @api private
|
279
|
+
def _base_rules
|
280
|
+
lambda do
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# @since 0.6.0
|
285
|
+
# @api private
|
286
|
+
def _schema_config
|
287
|
+
lambda do |config|
|
288
|
+
config.messages = _messages unless _messages.nil?
|
289
|
+
config.messages_file = _messages_path unless _messages_path.nil?
|
290
|
+
config.namespace = namespace
|
291
|
+
|
292
|
+
require "rdy/validation/messages/i18n" if config.messages == :i18n
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# @since 0.6.0
|
297
|
+
# @api private
|
298
|
+
def _schema_predicates
|
299
|
+
return if _predicates_module.nil? && _predicates.empty?
|
300
|
+
|
301
|
+
lambda do |config|
|
302
|
+
config.messages = _predicates_module&.messages || @_messages || DEFAULT_MESSAGES_ENGINE
|
303
|
+
config.messages_file = _predicates_module.messages_path unless _predicates_module.nil?
|
304
|
+
|
305
|
+
require "rdy/validation/messages/i18n" if config.messages == :i18n
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
# @since 0.6.0
|
310
|
+
# @api private
|
311
|
+
def __predicates
|
312
|
+
mod = _predicates_module || Module.new { include Hanami::Validations::Predicates }
|
313
|
+
|
314
|
+
_predicates.each do |p|
|
315
|
+
mod.module_eval do
|
316
|
+
predicate(p.name, &p.to_proc)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
mod
|
321
|
+
end
|
322
|
+
|
323
|
+
# @since 0.6.0
|
324
|
+
# @api private
|
325
|
+
def __messages
|
326
|
+
result = _predicates.each_with_object({}) do |p, ret|
|
327
|
+
ret[p.name] = p.message
|
328
|
+
end
|
329
|
+
|
330
|
+
# @api private
|
331
|
+
Module.new do
|
332
|
+
@@__messages = result # rubocop:disable Style/ClassVars
|
333
|
+
|
334
|
+
# @api private
|
335
|
+
def self.extended(base)
|
336
|
+
base.instance_eval do
|
337
|
+
def __messages
|
338
|
+
Hash[en: {errors: @@__messages}]
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# @api private
|
344
|
+
def messages
|
345
|
+
engine = super
|
346
|
+
|
347
|
+
messages = if engine.respond_to?(:merge)
|
348
|
+
engine
|
349
|
+
else
|
350
|
+
engine.messages
|
351
|
+
end
|
352
|
+
|
353
|
+
config.messages == :i18n ? messages : messages.merge(__messages)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
# Initialize a new instance of a validator
|
360
|
+
#
|
361
|
+
# @param input [#to_h] a set of input data
|
362
|
+
#
|
363
|
+
# @since 0.6.0
|
364
|
+
def initialize(input = {})
|
365
|
+
@input = input.to_h
|
366
|
+
end
|
367
|
+
|
368
|
+
# Validates the object.
|
369
|
+
#
|
370
|
+
# @return [Rdy::Validations::Result]
|
371
|
+
#
|
372
|
+
# @since 0.2.4
|
373
|
+
def validate
|
374
|
+
self.class.schema.call(@input)
|
375
|
+
end
|
376
|
+
|
377
|
+
# Returns a Hash with the defined attributes as symbolized keys, and their
|
378
|
+
# relative values.
|
379
|
+
#
|
380
|
+
# @return [Hash]
|
381
|
+
#
|
382
|
+
# @since 0.1.0
|
383
|
+
def to_h
|
384
|
+
validate.output
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hanami-ruby3
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ascenda Developers
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-02
|
11
|
+
date: 2023-03-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hanami
|
@@ -53,6 +53,14 @@ files:
|
|
53
53
|
- lib/hanami/ruby3/hanami/view/rendering/scope.rb
|
54
54
|
- lib/hanami/ruby3/hanami/views/default.rb
|
55
55
|
- lib/hanami/ruby3/version.rb
|
56
|
+
- lib/hanami/v1/README.md
|
57
|
+
- lib/hanami/v1/action/base_params.rb
|
58
|
+
- lib/hanami/v1/action/params.rb
|
59
|
+
- lib/hanami/v1/validations.rb
|
60
|
+
- lib/hanami/v1/validations/form.rb
|
61
|
+
- lib/hanami/v1/validations/inline_predicate.rb
|
62
|
+
- lib/hanami/v1/validations/namespace.rb
|
63
|
+
- lib/hanami/v1/validations/predicates.rb
|
56
64
|
homepage: http://github.com/Kaligo/hanami-ruby3
|
57
65
|
licenses: []
|
58
66
|
metadata:
|