pb-serializer 0.1.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +10 -2
- data/.github/workflows/review.yml +5 -5
- data/CHANGELOG.md +42 -0
- data/README.md +26 -16
- data/codecov.yml +6 -0
- data/lib/pb/serializable.rb +110 -37
- data/lib/pb/serializer.rb +49 -0
- data/lib/pb/serializer/attribute.rb +44 -9
- data/lib/pb/serializer/base.rb +19 -1
- data/lib/pb/serializer/normalized_mask.rb +57 -0
- data/lib/pb/serializer/oneof.rb +3 -3
- data/lib/pb/serializer/version.rb +1 -1
- data/pb-serializer.gemspec +3 -1
- metadata +39 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b46a686595d5e9e919c21ff7e6902cec71be97d716085dc38b8b2ef0192e7cf1
|
4
|
+
data.tar.gz: c453986c407e5e567bce43f3f45429697c552dedc44356ffee2a8e0b4d5eaea7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c58c5ab0714bd7bee52f3f30ede95b676b849cdf0be482a9de0187287d3ecfd0e758b6cc7c17831965d7e8e54fc13763c6290607330077c3a2135c6fe9cfd439
|
7
|
+
data.tar.gz: 7b3cc2224f7550c72714cc21c1e656182891788b1f0aa67b02dce177ea44cb6dfef5115073aeb08eb130fe40decc15551e73bbb2aa6ec77c0fc997e8fbf3477f
|
data/.github/workflows/ci.yml
CHANGED
@@ -9,8 +9,10 @@ jobs:
|
|
9
9
|
fail-fast: false
|
10
10
|
matrix:
|
11
11
|
os: [ ubuntu-latest, macos-latest ]
|
12
|
-
#
|
13
|
-
|
12
|
+
# TODO: Wait for supporting Ruby 3.0 in simplecov-cobertura.
|
13
|
+
# See https://github.com/dashingrocket/simplecov-cobertura/pull/16
|
14
|
+
# ruby: [ 2.5, 2.6, 2.7, 3.0 ]
|
15
|
+
ruby: [ 2.5, 2.6, 2.7 ]
|
14
16
|
runs-on: ${{ matrix.os }}
|
15
17
|
|
16
18
|
steps:
|
@@ -30,3 +32,9 @@ jobs:
|
|
30
32
|
- run: bundle install --jobs 4 --retry 3
|
31
33
|
|
32
34
|
- run: bundle exec rspec
|
35
|
+
env:
|
36
|
+
CI: true
|
37
|
+
|
38
|
+
- uses: codecov/codecov-action@v1
|
39
|
+
with:
|
40
|
+
file: ./coverage/coverage.xml
|
@@ -15,11 +15,9 @@ jobs:
|
|
15
15
|
sudo apt-get update
|
16
16
|
sudo apt-get install libsqlite3-dev
|
17
17
|
|
18
|
-
-
|
19
|
-
|
20
|
-
|
21
|
-
curl -sfL https://raw.githubusercontent.com/reviewdog/reviewdog/master/install.sh | sh -s -- -b $HOME/bin
|
22
|
-
echo ::add-path::$HOME/bin
|
18
|
+
- uses: reviewdog/action-setup@v1
|
19
|
+
with:
|
20
|
+
reviewdog_version: latest
|
23
21
|
|
24
22
|
- run: gem install bundler -v 2.1.4
|
25
23
|
|
@@ -28,6 +26,8 @@ jobs:
|
|
28
26
|
- run: bundle install --jobs 4 --retry 3
|
29
27
|
|
30
28
|
- name: Run Rubocop with Reviewdog
|
29
|
+
env:
|
30
|
+
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
31
31
|
run: |
|
32
32
|
bundle exec rubocop --fail-level E \
|
33
33
|
| reviewdog -f=rubocop -reporter=github-pr-review
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
## Unreleased
|
2
|
+
|
3
|
+
## 0.5.0
|
4
|
+
|
5
|
+
- Bump `computed_model` from 0.2.2 to 0.3.0 https://github.com/wantedly/pb-serializer/pull/38
|
6
|
+
|
7
|
+
## 0.4.0
|
8
|
+
|
9
|
+
- Make `#initialize` extensible used with `define_primary_loader` https://github.com/wantedly/pb-serializer/pull/31
|
10
|
+
- Supoprt `ignore` directive https://github.com/wantedly/pb-serializer/pull/36
|
11
|
+
- Support field mask https://github.com/wantedly/pb-serializer/pull/34
|
12
|
+
|
13
|
+
## 0.3.0
|
14
|
+
|
15
|
+
- Support `if` option https://github.com/wantedly/pb-serializer/pull/24
|
16
|
+
- Improve error handling https://github.com/wantedly/pb-serializer/pull/26
|
17
|
+
- raise `MissingMessageTypeError` if `message` declaration is missed
|
18
|
+
- raise `MissingFieldError` if `attribute` declaration is missed
|
19
|
+
- raise `InvalidOptionError` when `attribute` receives invalid params
|
20
|
+
- Introduce Pb::Serializer.configure https://github.com/wantedly/pb-serializer/pull/27
|
21
|
+
- Add `missing_field_behavior` config to suppress `MissingFieldError`
|
22
|
+
- Rename `InvalidOptionError` -> `InvalidAttributeOptionError`
|
23
|
+
- Skip serializing when a value is already serialized https://github.com/wantedly/pb-serializer/pull/29
|
24
|
+
|
25
|
+
|
26
|
+
## 0.2.1
|
27
|
+
|
28
|
+
- **BREAKING CHANGE** `required` -> `allow_nil` https://github.com/wantedly/pb-serializer/pull/21
|
29
|
+
- Make Serializer's constructors extensible https://github.com/wantedly/pb-serializer/pull/22
|
30
|
+
|
31
|
+
## 0.2.0
|
32
|
+
|
33
|
+
- **BREAKING CHANGE** https://github.com/wantedly/pb-serializer/pull/17
|
34
|
+
- Support loading and serializing arrays
|
35
|
+
- Bump `computed_model` from 0.1.0 to 0.2.1
|
36
|
+
- Change API
|
37
|
+
- Add example specs https://github.com/wantedly/pb-serializer/pull/18
|
38
|
+
|
39
|
+
|
40
|
+
## 0.1.0
|
41
|
+
|
42
|
+
Initial release.
|
data/README.md
CHANGED
@@ -1,32 +1,41 @@
|
|
1
1
|
# Pb::Serializer
|
2
|
+
[](https://github.com/wantedly/pb-serializer/actions?query=workflow%3ACI+branch%3Amaster)
|
3
|
+
[](https://codecov.io/gh/wantedly/pb-serializer)
|
4
|
+
[](https://badge.fury.io/rb/pb-serializer)
|
5
|
+
[](./LICENSE)
|
2
6
|
|
3
7
|
```rb
|
4
8
|
class UserSerializer < Pb::Serializer::Base
|
5
9
|
message YourApp::User
|
6
10
|
|
7
|
-
attribute :id
|
8
|
-
attribute :name
|
9
|
-
attribute :posts
|
11
|
+
attribute :id
|
12
|
+
attribute :name
|
13
|
+
attribute :posts
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
define_primary_loader :user do |subdeps, ids:, **|
|
16
|
+
User.where(id: ids).preload(subdeps).map { |u| new(u) }
|
17
|
+
end
|
18
|
+
|
19
|
+
define_loader :posts, key: -> { id } do |user_ids, subdeps, **|
|
20
|
+
PostSerializer.bulk_load(user_id: user_ids, with: subdeps).group_by { |s| s.post.user_id }
|
16
21
|
end
|
17
22
|
|
18
23
|
dependency :posts
|
19
24
|
computed def post_count
|
20
|
-
|
25
|
+
posts.count
|
21
26
|
end
|
22
27
|
end
|
23
28
|
|
24
29
|
class PostSerializer < Pb::Serializer::Base
|
25
30
|
message YourApp::Post
|
26
31
|
|
27
|
-
|
28
|
-
|
29
|
-
|
32
|
+
define_primary_loader :post do |subdeps, user_ids:, **|
|
33
|
+
Post.where(user_id: user_ids).preload(subdeps).map { |p| new(p) }
|
34
|
+
end
|
35
|
+
|
36
|
+
attribute :id
|
37
|
+
attribute :title
|
38
|
+
attribute :body
|
30
39
|
end
|
31
40
|
|
32
41
|
class UserGrpcService < YourApp::UserService::Service
|
@@ -34,22 +43,23 @@ class UserGrpcService < YourApp::UserService::Service
|
|
34
43
|
# @param call [GRPC::ActiveCall::SingleReqView]
|
35
44
|
# @return [YourApp::User]
|
36
45
|
def get_users(req, call)
|
37
|
-
|
38
|
-
UserSerializer.serialize(user, with: req.field_mask)
|
46
|
+
UserSerializer.bulk_load_and_serialize(ids: [req.user_id], with: req.field_mask)[0]
|
39
47
|
end
|
40
48
|
|
41
49
|
# @param req [YourApp::ListFriendUsersRequest]
|
42
50
|
# @param call [GRPC::ActiveCall::SingleReqView]
|
43
51
|
# @return [YourApp::ListFriendUsersResponse]
|
44
52
|
def list_friend_users(req, call)
|
45
|
-
|
53
|
+
current_user = User.find(current_user_id)
|
46
54
|
YourApp::ListFriendUsersResponse.new(
|
47
|
-
users: UserSerializer.
|
55
|
+
users: UserSerializer.bulk_load_and_serialize(ids: current_user.friend_ids, with: req.field_mask)
|
48
56
|
)
|
49
57
|
end
|
50
58
|
end
|
51
59
|
```
|
52
60
|
|
61
|
+
More examples are available under [./spec/examples](./spec/examples).
|
62
|
+
|
53
63
|
|
54
64
|
## Installation
|
55
65
|
|
data/codecov.yml
ADDED
data/lib/pb/serializable.rb
CHANGED
@@ -1,16 +1,21 @@
|
|
1
1
|
module Pb
|
2
2
|
module Serializable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include ComputedModel::Model
|
3
5
|
def self.included(base)
|
4
6
|
base.extend ClassMethods
|
5
|
-
base.
|
7
|
+
base.singleton_class.prepend Hook
|
6
8
|
end
|
7
9
|
|
10
|
+
# @param with [
|
11
|
+
# Google::Protobuf::FieldMask,
|
12
|
+
# Array<(Symbol, Hash)>,
|
13
|
+
# Hash{Symbol=>(Array,Symbol,Hash)},
|
14
|
+
# Pb::Serializer::NormalizedMask
|
15
|
+
# ]
|
8
16
|
def to_pb(with: nil)
|
9
|
-
|
10
|
-
|
11
|
-
end
|
12
|
-
|
13
|
-
self.class.bulk_load_and_compute(Array(self), with)
|
17
|
+
with ||= ::Pb::Serializer.build_default_mask(self.class.message_class.descriptor)
|
18
|
+
with = ::Pb::Serializer::NormalizedMask.build(with)
|
14
19
|
|
15
20
|
oneof_set = []
|
16
21
|
|
@@ -18,26 +23,38 @@ module Pb
|
|
18
23
|
self.class.message_class.descriptor.each do |fd|
|
19
24
|
attr = self.class.find_attribute_by_field_descriptor(fd)
|
20
25
|
|
21
|
-
|
26
|
+
unless attr
|
27
|
+
msg = "#{self.class.message_class.name}.#{fd.name} is missed in #{self.class.name}"
|
22
28
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
29
|
+
case Pb::Serializer.configuration.missing_field_behavior
|
30
|
+
when :raise then raise ::Pb::Serializer::MissingFieldError, msg
|
31
|
+
when :warn then Pb::Serializer.logger.warn msg
|
32
|
+
end
|
27
33
|
|
28
|
-
|
29
|
-
raise ::Pb::Serializer::ValidationError, "#{object.class.name}##{attr.name} is required"
|
34
|
+
next
|
30
35
|
end
|
31
36
|
|
32
|
-
next
|
37
|
+
next unless with.key?(attr.name)
|
38
|
+
next unless attr.serializable?(self)
|
39
|
+
|
40
|
+
raise "#{self.name}.#{attr.name} is not defined" unless respond_to?(attr.name)
|
41
|
+
|
42
|
+
v = public_send(attr.name)
|
43
|
+
v = attr.convert_to_pb(v, with: with[attr.name])
|
33
44
|
|
34
45
|
if attr.oneof?
|
35
|
-
if
|
36
|
-
|
46
|
+
if !v.nil?
|
47
|
+
if oneof_set.include?(attr.oneof)
|
48
|
+
raise ::Pb::Serializer::ConflictOneofError, "#{primary_object.class.name}##{attr.name} is oneof attribute"
|
49
|
+
end
|
50
|
+
oneof_set << attr.oneof
|
37
51
|
end
|
38
|
-
|
52
|
+
elsif !attr.allow_nil? && v.nil?
|
53
|
+
raise ::Pb::Serializer::ValidationError, "#{primary_object.class.name}##{attr.name} is required"
|
39
54
|
end
|
40
55
|
|
56
|
+
next if v.nil?
|
57
|
+
|
41
58
|
if attr.repeated?
|
42
59
|
o.public_send(attr.name).push(*v)
|
43
60
|
else
|
@@ -47,30 +64,67 @@ module Pb
|
|
47
64
|
|
48
65
|
self.class.oneofs.each do |oneof|
|
49
66
|
next if oneof_set.include?(oneof.name)
|
50
|
-
next
|
51
|
-
raise ::Pb::Serializer::ValidationError, "#{
|
67
|
+
next if oneof.allow_nil?
|
68
|
+
raise ::Pb::Serializer::ValidationError, "#{primary_object.class.name}##{oneof.name} is required"
|
52
69
|
end
|
53
70
|
|
54
71
|
o
|
55
72
|
end
|
56
73
|
|
74
|
+
private def primary_object
|
75
|
+
primary_object_name = self.class.__pb_serializer_primary_model_name
|
76
|
+
if primary_object_name
|
77
|
+
send(primary_object_name)
|
78
|
+
elsif kind_of?(Serializer::Base)
|
79
|
+
send(:object)
|
80
|
+
else
|
81
|
+
self
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
module Hook
|
86
|
+
def define_primary_loader(name)
|
87
|
+
self.__pb_serializer_primary_model_name = name
|
88
|
+
|
89
|
+
super
|
90
|
+
end
|
91
|
+
|
92
|
+
def computed(name)
|
93
|
+
__pb_serializer_attrs << name
|
94
|
+
|
95
|
+
super
|
96
|
+
end
|
97
|
+
|
98
|
+
def define_loader(name, **)
|
99
|
+
__pb_serializer_attrs << name
|
100
|
+
|
101
|
+
super
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
57
105
|
module ClassMethods
|
58
106
|
attr_reader :message_class
|
107
|
+
attr_accessor :__pb_serializer_primary_model_name
|
108
|
+
|
59
109
|
def message(klass)
|
60
110
|
@message_class = klass
|
61
111
|
end
|
62
112
|
|
63
113
|
# @param name [Symbol] An attribute name
|
64
|
-
# @param
|
65
|
-
# @
|
66
|
-
|
114
|
+
# @param [Hash] opts options
|
115
|
+
# @option opts [Boolean] :allow_nil Set true if this attribute allow to be nil
|
116
|
+
# @option opts [Class] :serializer A serializer class for this attribute
|
117
|
+
# @option opts [String, Symbol, Proc] :if A method, proc or string to call to determine to serialize this field
|
118
|
+
def attribute(name, opts = {})
|
119
|
+
raise ::Pb::Serializer::MissingMessageTypeError, "message specificaiton is missed" unless message_class
|
120
|
+
|
67
121
|
fd = message_class.descriptor.find { |fd| fd.name.to_sym == name }
|
122
|
+
|
68
123
|
raise ::Pb::Serializer::UnknownFieldError, "#{name} is not defined in #{message_class.name}" unless fd
|
69
124
|
|
70
125
|
attr = ::Pb::Serializer::Attribute.new(
|
71
126
|
name: name,
|
72
|
-
|
73
|
-
serializer_class: serializer,
|
127
|
+
options: opts,
|
74
128
|
field_descriptor: fd,
|
75
129
|
oneof: @current_oneof&.name,
|
76
130
|
)
|
@@ -79,28 +133,43 @@ module Pb
|
|
79
133
|
@attr_by_name[name] = attr
|
80
134
|
|
81
135
|
define_method attr.name do
|
82
|
-
|
136
|
+
primary_object.public_send(attr.name)
|
83
137
|
end
|
138
|
+
end
|
84
139
|
|
85
|
-
|
86
|
-
|
140
|
+
# @param names [Array<Symbol>] Attribute names to be ignored
|
141
|
+
def ignore(*names)
|
142
|
+
names.each do |name|
|
143
|
+
attribute name, ignore: true
|
144
|
+
end
|
87
145
|
end
|
88
146
|
|
89
|
-
# @param
|
90
|
-
# @
|
91
|
-
def
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
147
|
+
# @param with [Array, Hash, Google::Protobuf::FieldMask, nil]
|
148
|
+
# @return [Array]
|
149
|
+
def bulk_load_and_serialize(with: nil, **args)
|
150
|
+
bulk_load(with: with, **args).map { |s| s.to_pb(with: with) }
|
151
|
+
end
|
152
|
+
|
153
|
+
def bulk_load(with: nil, **args)
|
154
|
+
with ||= ::Pb::Serializer.build_default_mask(message_class.descriptor)
|
155
|
+
with = ::Pb::Serializer::NormalizedMask.build(with)
|
156
|
+
with = with.reject { |c| (__pb_serializer_attrs & (c.kind_of?(Hash) ? c.keys : [c])).empty? }
|
157
|
+
|
158
|
+
primary_object_name = __pb_serializer_primary_model_name
|
159
|
+
if primary_object_name
|
160
|
+
(with[primary_object_name] ||= []) << true
|
161
|
+
elsif self < Serializer::Base
|
162
|
+
(with[:object] ||= []) << true
|
96
163
|
end
|
164
|
+
|
165
|
+
bulk_load_and_compute(with, **args)
|
97
166
|
end
|
98
167
|
|
99
|
-
def oneof(name,
|
168
|
+
def oneof(name, allow_nil: false)
|
100
169
|
@oneof_by_name ||= {}
|
101
170
|
@current_oneof = ::Pb::Serializer::Oneof.new(
|
102
171
|
name: name,
|
103
|
-
|
172
|
+
allow_nil: allow_nil,
|
104
173
|
attributes: [],
|
105
174
|
)
|
106
175
|
yield
|
@@ -108,10 +177,14 @@ module Pb
|
|
108
177
|
@current_oneof = nil
|
109
178
|
end
|
110
179
|
|
180
|
+
private def __pb_serializer_attrs
|
181
|
+
@__pb_serializer_attrs ||= Set.new
|
182
|
+
end
|
183
|
+
|
111
184
|
# @param fd [Google::Protobuf::FieldDescriptor] a field descriptor
|
112
185
|
# @return [Pb::Serializer::Attribute, nil]
|
113
186
|
def find_attribute_by_field_descriptor(fd)
|
114
|
-
@attr_by_name[fd.name.to_sym]
|
187
|
+
(@attr_by_name || {})[fd.name.to_sym]
|
115
188
|
end
|
116
189
|
|
117
190
|
def oneofs
|
data/lib/pb/serializer.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
require "pb/serializer/version"
|
2
2
|
require "the_pb"
|
3
3
|
require "computed_model"
|
4
|
+
require "google/protobuf/field_mask_pb"
|
4
5
|
|
5
6
|
require "pb/serializable"
|
7
|
+
require "pb/serializer/normalized_mask"
|
6
8
|
require "pb/serializer/base"
|
7
9
|
require "pb/serializer/attribute"
|
8
10
|
require "pb/serializer/oneof"
|
@@ -10,11 +12,58 @@ require "pb/serializer/oneof"
|
|
10
12
|
module Pb
|
11
13
|
module Serializer
|
12
14
|
class Error < StandardError; end
|
15
|
+
class InvalidConfigurationError < Error; end
|
16
|
+
class MissingMessageTypeError < Error; end
|
13
17
|
class UnknownFieldError < Error; end
|
14
18
|
class ValidationError < Error; end
|
15
19
|
class ConflictOneofError < Error; end
|
20
|
+
class InvalidAttributeOptionError < Error; end
|
21
|
+
class MissingFieldError < Error; end
|
22
|
+
|
23
|
+
class Configuration
|
24
|
+
# @!attribute logger
|
25
|
+
# @return [Logger]
|
26
|
+
attr_accessor :logger
|
27
|
+
# @!attribute [r] missing_field_behavior
|
28
|
+
# @return [:raise, :warn, :ignore] default: `:raise`
|
29
|
+
attr_reader :missing_field_behavior
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
self.missing_field_behavior = :raise
|
33
|
+
self.logger = Logger.new(STDOUT)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param v [:raise, :warn, :ignore]
|
37
|
+
def missing_field_behavior=(v)
|
38
|
+
@missing_field_behavior = v
|
39
|
+
|
40
|
+
unless %i(raise warn ignore).include?(v)
|
41
|
+
raise InvalidConfigurationError, "missing_field_behavior #{v} is not allowed"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
16
45
|
|
17
46
|
class << self
|
47
|
+
# @example
|
48
|
+
# Pb::Serializer.configuration do |c|
|
49
|
+
# c.missing_field_behavior = :raise # :raise, :warn or :ignore (defualt: :raise)
|
50
|
+
# end
|
51
|
+
# @yield [c]
|
52
|
+
# @yieldparam [Configuration] config
|
53
|
+
def configure
|
54
|
+
yield configuration
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Pb::Serializer::Configuration]
|
58
|
+
def configuration
|
59
|
+
@configuraiton ||= Configuration.new
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Logger]
|
63
|
+
def logger
|
64
|
+
configuration.logger
|
65
|
+
end
|
66
|
+
|
18
67
|
# @param [Google::Protobuf::Descriptor]
|
19
68
|
def build_default_mask(descriptor)
|
20
69
|
set =
|
@@ -2,16 +2,31 @@ module Pb
|
|
2
2
|
module Serializer
|
3
3
|
class Attribute < Struct.new(
|
4
4
|
:name,
|
5
|
-
:
|
6
|
-
:serializer_class,
|
5
|
+
:options,
|
7
6
|
:field_descriptor,
|
8
7
|
:oneof,
|
9
8
|
keyword_init: true,
|
10
9
|
)
|
11
10
|
|
11
|
+
ALLOWED_OPTIONS = Set[:allow_nil, :if, :serializer, :ignore].freeze
|
12
|
+
|
13
|
+
def initialize(options:, **)
|
14
|
+
super
|
15
|
+
|
16
|
+
unknown_options = options.keys.to_set - ALLOWED_OPTIONS
|
17
|
+
unless unknown_options.empty?
|
18
|
+
raise InvalidAttributeOptionError, "unknown options are specified in #{name} attribute: #{unknown_options.to_a}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
12
22
|
# @return [Boolean]
|
13
|
-
def
|
14
|
-
|
23
|
+
def allow_nil?
|
24
|
+
options.fetch(:allow_nil, false)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Class]
|
28
|
+
def serializer_class
|
29
|
+
options[:serializer]
|
15
30
|
end
|
16
31
|
|
17
32
|
# @return [Boolean]
|
@@ -19,16 +34,37 @@ module Pb
|
|
19
34
|
field_descriptor.label == :repeated
|
20
35
|
end
|
21
36
|
|
37
|
+
# @return [Boolean]
|
38
|
+
def serializable?(s)
|
39
|
+
return false if options[:ignore]
|
40
|
+
|
41
|
+
cond = options[:if]
|
42
|
+
|
43
|
+
return true unless cond
|
44
|
+
|
45
|
+
case cond
|
46
|
+
when String, Symbol; then s.send(cond)
|
47
|
+
when Proc; then s.instance_exec(&cond)
|
48
|
+
else raise InvalidAttributeOptionError, "`if` option can accept only Symbol, String or Proc. but got #{cond.class}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
22
52
|
def oneof?
|
23
53
|
!oneof.nil?
|
24
54
|
end
|
25
55
|
|
26
56
|
# @param v [Object]
|
27
|
-
|
28
|
-
|
57
|
+
# @param with [Pb::Serializer::NormalizedMask]
|
58
|
+
def convert_to_pb(v, with: nil, should_repeat: repeated?)
|
59
|
+
return nil if v.nil?
|
60
|
+
return v.map { |i| convert_to_pb(i, should_repeat: false, with: with) } if should_repeat
|
29
61
|
|
30
62
|
case field_descriptor.type
|
31
63
|
when :message
|
64
|
+
if v.class < Google::Protobuf::MessageExts && v.class.descriptor.name == field_descriptor.submsg_name
|
65
|
+
return v
|
66
|
+
end
|
67
|
+
|
32
68
|
case field_descriptor.submsg_name
|
33
69
|
when "google.protobuf.Timestamp" then Pb.to_timestamp(v)
|
34
70
|
when "google.protobuf.StringValue" then Pb.to_strval(v)
|
@@ -41,9 +77,8 @@ module Pb
|
|
41
77
|
when "google.protobuf.BoolValue" then Pb.to_boolval(v)
|
42
78
|
when "google.protobuf.BytesValue" then Pb.to_bytesval(v)
|
43
79
|
else
|
44
|
-
return
|
45
|
-
return
|
46
|
-
return v.to_pb if v.kind_of?(::Pb::Serializable)
|
80
|
+
return serializer_class.new(v).to_pb(with: with) if serializer_class
|
81
|
+
return v.to_pb(with: with) if v.kind_of?(::Pb::Serializable)
|
47
82
|
|
48
83
|
raise "serializer was not found for #{field_descriptor.submsg_name}"
|
49
84
|
end
|
data/lib/pb/serializer/base.rb
CHANGED
@@ -3,13 +3,31 @@ module Pb
|
|
3
3
|
class Base
|
4
4
|
def self.inherited(base)
|
5
5
|
base.include ::Pb::Serializable
|
6
|
+
base.singleton_class.prepend Hook
|
6
7
|
end
|
7
8
|
|
8
9
|
attr_reader :object
|
9
10
|
|
10
|
-
def initialize(object)
|
11
|
+
def initialize(object, *)
|
11
12
|
@object = object
|
12
13
|
end
|
14
|
+
|
15
|
+
module Hook
|
16
|
+
def define_primary_loader(name, &block)
|
17
|
+
class_eval <<~RUBY
|
18
|
+
module PbSerializerDefinePrimaryLoaderPrependMethods
|
19
|
+
def initialize(*args)
|
20
|
+
super
|
21
|
+
@#{name} = object
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
prepend PbSerializerDefinePrimaryLoaderPrependMethods
|
26
|
+
RUBY
|
27
|
+
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
13
31
|
end
|
14
32
|
end
|
15
33
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Pb::Serializer
|
2
|
+
class NormalizedMask < ::Hash
|
3
|
+
class << self
|
4
|
+
# @param [Google::Protobuf::FieldMask, Symbol, Array<(Symbol,Hash)>, Hash{Symbol=>(Array,Symbol,Hash)}]
|
5
|
+
# @return [Hash{Symbol=>Hash}]
|
6
|
+
def build(input)
|
7
|
+
return input if input.kind_of? self
|
8
|
+
|
9
|
+
normalized = new
|
10
|
+
|
11
|
+
case input
|
12
|
+
when Google::Protobuf::FieldMask
|
13
|
+
normalized = normalize_mask_paths(input.paths)
|
14
|
+
when Array
|
15
|
+
input.each do |v|
|
16
|
+
deep_merge!(normalized, build(v))
|
17
|
+
end
|
18
|
+
when Hash
|
19
|
+
input.each do |k, v|
|
20
|
+
normalized[k] ||= new
|
21
|
+
deep_merge!(normalized[k], build(v))
|
22
|
+
end
|
23
|
+
when Symbol
|
24
|
+
normalized[input] ||= new
|
25
|
+
else
|
26
|
+
raise "not supported field mask type: #{input.class}"
|
27
|
+
end
|
28
|
+
|
29
|
+
normalized
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# @param [Array<String>]
|
35
|
+
# @return [Hash{Symbol=>Hash}]
|
36
|
+
def normalize_mask_paths(paths)
|
37
|
+
paths_by_key = {}
|
38
|
+
|
39
|
+
paths.each do |path|
|
40
|
+
key, rest = path.split('.', 2)
|
41
|
+
paths_by_key[key.to_sym] ||= []
|
42
|
+
paths_by_key[key.to_sym].push(rest) if rest && !rest.empty?
|
43
|
+
end
|
44
|
+
|
45
|
+
paths_by_key.keys.each_with_object(new) do |key, normalized|
|
46
|
+
normalized[key] = normalize_mask_paths(paths_by_key[key])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def deep_merge!(h1, h2)
|
51
|
+
h1.merge!(h2) do |_k, v1, v2|
|
52
|
+
deep_merge!(v1, v2)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/pb/serializer/oneof.rb
CHANGED
data/pb-serializer.gemspec
CHANGED
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
|
|
29
29
|
rails_versions = [">= 5.2", "< 6.1"]
|
30
30
|
spec.add_runtime_dependency "google-protobuf", "~> 3.0"
|
31
31
|
spec.add_runtime_dependency "the_pb", "~> 0.0.1"
|
32
|
-
spec.add_runtime_dependency "computed_model", "~> 0.
|
32
|
+
spec.add_runtime_dependency "computed_model", "~> 0.3.0"
|
33
33
|
|
34
34
|
spec.add_development_dependency "activerecord", rails_versions
|
35
35
|
spec.add_development_dependency "bundler", "~> 2.0"
|
@@ -38,4 +38,6 @@ Gem::Specification.new do |spec|
|
|
38
38
|
spec.add_development_dependency "rspec", "~> 3.0"
|
39
39
|
spec.add_development_dependency "rubocop", "0.67.2" # for onkcop
|
40
40
|
spec.add_development_dependency "sqlite3", "~> 1.4"
|
41
|
+
spec.add_development_dependency "simplecov", "~> 0.18.5"
|
42
|
+
spec.add_development_dependency "simplecov-cobertura", "~> 1.3"
|
41
43
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pb-serializer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- izumin5210
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-07-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: google-protobuf
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 0.
|
47
|
+
version: 0.3.0
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0.
|
54
|
+
version: 0.3.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: activerecord
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -156,6 +156,34 @@ dependencies:
|
|
156
156
|
- - "~>"
|
157
157
|
- !ruby/object:Gem::Version
|
158
158
|
version: '1.4'
|
159
|
+
- !ruby/object:Gem::Dependency
|
160
|
+
name: simplecov
|
161
|
+
requirement: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - "~>"
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: 0.18.5
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - "~>"
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: 0.18.5
|
173
|
+
- !ruby/object:Gem::Dependency
|
174
|
+
name: simplecov-cobertura
|
175
|
+
requirement: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - "~>"
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '1.3'
|
180
|
+
type: :development
|
181
|
+
prerelease: false
|
182
|
+
version_requirements: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - "~>"
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '1.3'
|
159
187
|
description: Serialize objects into Protocol Buffers messages
|
160
188
|
email:
|
161
189
|
- m@izum.in
|
@@ -169,6 +197,7 @@ files:
|
|
169
197
|
- ".rspec"
|
170
198
|
- ".rubocop.yml"
|
171
199
|
- ".rubocop_todo.yml"
|
200
|
+
- CHANGELOG.md
|
172
201
|
- CODE_OF_CONDUCT.md
|
173
202
|
- Gemfile
|
174
203
|
- LICENSE.txt
|
@@ -176,10 +205,12 @@ files:
|
|
176
205
|
- Rakefile
|
177
206
|
- bin/console
|
178
207
|
- bin/setup
|
208
|
+
- codecov.yml
|
179
209
|
- lib/pb/serializable.rb
|
180
210
|
- lib/pb/serializer.rb
|
181
211
|
- lib/pb/serializer/attribute.rb
|
182
212
|
- lib/pb/serializer/base.rb
|
213
|
+
- lib/pb/serializer/normalized_mask.rb
|
183
214
|
- lib/pb/serializer/oneof.rb
|
184
215
|
- lib/pb/serializer/version.rb
|
185
216
|
- pb-serializer.gemspec
|
@@ -190,7 +221,7 @@ metadata:
|
|
190
221
|
homepage_uri: https://github.com/izumin5210/pb-serializer
|
191
222
|
source_code_uri: https://github.com/izumin5210/pb-serializer
|
192
223
|
changelog_uri: https://github.com/izumin5210/pb-serializer
|
193
|
-
post_install_message:
|
224
|
+
post_install_message:
|
194
225
|
rdoc_options: []
|
195
226
|
require_paths:
|
196
227
|
- lib
|
@@ -205,8 +236,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
205
236
|
- !ruby/object:Gem::Version
|
206
237
|
version: '0'
|
207
238
|
requirements: []
|
208
|
-
rubygems_version: 3.
|
209
|
-
signing_key:
|
239
|
+
rubygems_version: 3.2.3
|
240
|
+
signing_key:
|
210
241
|
specification_version: 4
|
211
242
|
summary: Serialize objects into Protocol Buffers messages
|
212
243
|
test_files: []
|