pb-serializer 0.5.0 → 0.5.2
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/.yardopts +8 -0
- data/CHANGELOG.md +12 -0
- data/README.ja.md +66 -0
- data/README.md +46 -46
- data/docs/examples.md +236 -0
- data/lib/pb/serializable/computed_model_support.rb +52 -0
- data/lib/pb/serializable/dsl/attribute.rb +94 -0
- data/lib/pb/serializable/dsl/oneof.rb +18 -0
- data/lib/pb/serializable/dsl.rb +75 -0
- data/lib/pb/serializable.rb +38 -105
- data/lib/pb/serializer/base.rb +3 -0
- data/lib/pb/serializer/version.rb +1 -1
- data/lib/pb/serializer.rb +46 -5
- data/pb-serializer.gemspec +2 -2
- metadata +16 -12
- data/lib/pb/serializer/attribute.rb +0 -91
- data/lib/pb/serializer/normalized_mask.rb +0 -57
- data/lib/pb/serializer/oneof.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b06e7d595d2212e0d8c6d36bc652acccb3020094b6370c9494476eb0de483487
|
4
|
+
data.tar.gz: 13efc77cdd5a595a0763b16989f7064a5c1f79ebd3bdff7330964eadd7569db7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 936b879ffebc21afb812bb8763cfc57132c19db685138ba0b69c74ab156c70909a2f35482464c0c35ed776e5b2ff02546b1f55699bc48b5f44568890771da0b8
|
7
|
+
data.tar.gz: 6da97182764357672107dbacf6184e723f55ad203078dc9c660e53741d861ae39e210cf5d7ecb6a9b4155e618273e0963a97bc322bba7e579faa8f066b36a5bb
|
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
## Unreleased
|
2
2
|
|
3
|
+
## 0.5.2
|
4
|
+
|
5
|
+
- Generate the default mask lazily to prevent infinite recursions https://github.com/wantedly/pb-serializer/pull/52
|
6
|
+
|
7
|
+
## 0.5.1
|
8
|
+
|
9
|
+
- Improving interoperability with `computed_model`
|
10
|
+
- Simplify field mask normalizer and add `Pb::Serializer.parse_field_mask` method https://github.com/wantedly/pb-serializer/pull/40
|
11
|
+
- Stop defining accessor methods in `attribute` DSL if the method of the same name already existss https://github.com/wantedly/pb-serializer/pull/42
|
12
|
+
- Refactoring
|
13
|
+
- Extract Dsl and Hook from Serializable module https://github.com/wantedly/pb-serializer/pull/41
|
14
|
+
|
3
15
|
## 0.5.0
|
4
16
|
|
5
17
|
- Bump `computed_model` from 0.2.2 to 0.3.0 https://github.com/wantedly/pb-serializer/pull/38
|
data/README.ja.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
<!--
|
2
|
+
# @title 日本語版 README
|
3
|
+
-->
|
4
|
+
|
5
|
+
# Pb::Serializer
|
6
|
+
|
7
|
+
`Pb::Serializer` はRuby オブジェクトの Protocol Buffers シリアライザです。
|
8
|
+
|
9
|
+
[English version](./README.md)
|
10
|
+
|
11
|
+
## Features
|
12
|
+
|
13
|
+
- [ActiveModelSerializers](https://github.com/rails-api/active_model_serializers) のような宣言的な API
|
14
|
+
- [Well-Known Types](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf) への自動変換(例 `google.protobuf.Uint64Value`)
|
15
|
+
- [`google.protobuf.FieldMask`](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#fieldmask) を利用した、GraphQL のような選択的フィールド取得のサポート
|
16
|
+
- [ComputedModel](https://github.com/wantedly/computed_model) と組み合わせることで、複雑なロジックと依存関係を持つ API も宣言的に実装できます
|
17
|
+
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
以下のような Protocol Buffers のメッセージ定義および ActiveRecord モデルを例にします。
|
22
|
+
|
23
|
+
```proto
|
24
|
+
syntax = "proto3";
|
25
|
+
|
26
|
+
package example;
|
27
|
+
|
28
|
+
option ruby_package = "ExamplesPb";
|
29
|
+
|
30
|
+
message User {
|
31
|
+
uint64 id = 1;
|
32
|
+
string name = 2;
|
33
|
+
}
|
34
|
+
```
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# Schema: [id(integer), name(string)]
|
38
|
+
class User < ActiveRecord::Base
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
`.proto` で定義された `User` メッセージに対応する PbSerializer を実装します。
|
43
|
+
生成されたクラスと定義されているフィールドすべてを PbSerializer に宣言する必要があります。
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
class UserPbSerializer < Pb::Serializer::Base
|
47
|
+
message ExamplesPb::User
|
48
|
+
|
49
|
+
attribute :id
|
50
|
+
attribute :name
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
実装した PbSerializer で、Ruby オブジェクトを protobuf message object にシリアライズできます。
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
user = User.find(123)
|
58
|
+
UserPbSerializer.new(user).to_pb
|
59
|
+
# => <ExamplesPb::User: id: 123, name: "someuser">
|
60
|
+
```
|
61
|
+
|
62
|
+
各 attribute の値は、PbSerializer インスタンス、もしくはコンストラクタに渡されたオブジェクト から決定されます。
|
63
|
+
|
64
|
+
## Next read
|
65
|
+
|
66
|
+
- [Examples](./docs/examples.md)
|
data/README.md
CHANGED
@@ -4,62 +4,66 @@
|
|
4
4
|
[](https://badge.fury.io/rb/pb-serializer)
|
5
5
|
[](./LICENSE)
|
6
6
|
|
7
|
-
|
8
|
-
class UserSerializer < Pb::Serializer::Base
|
9
|
-
message YourApp::User
|
7
|
+
`Pb::Serializer` is Protocol Buffers serializer for Ruby objects.
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
[日本語版 README](./README.ja.md)
|
10
|
+
|
11
|
+
## Features
|
12
|
+
|
13
|
+
- Declarative APIs such as [ActiveModelSerializers](https://github.com/rails-api/active_model_serializers)
|
14
|
+
- Automatic conversion to [Well-Known Types](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf) (e.g. `google.protobuf.Uint64Value`)
|
15
|
+
- Support for GraphQL-like selective field fetching using [`google.protobuf.FieldMask`](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#fieldmask).
|
16
|
+
- When combined with [ComputedModel](https://github.com/wantedly/computed_model), APIs with complex logic and dependencies can be implemented declaratively.
|
17
|
+
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
The following is an example of a message definition and ActiveRecord model for Protocol Buffers.
|
22
|
+
|
23
|
+
```proto
|
24
|
+
syntax = "proto3";
|
14
25
|
|
15
|
-
|
16
|
-
User.where(id: ids).preload(subdeps).map { |u| new(u) }
|
17
|
-
end
|
26
|
+
package example;
|
18
27
|
|
19
|
-
|
20
|
-
PostSerializer.bulk_load(user_id: user_ids, with: subdeps).group_by { |s| s.post.user_id }
|
21
|
-
end
|
28
|
+
option ruby_package = "ExamplesPb";
|
22
29
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
30
|
+
message User {
|
31
|
+
uint64 id = 1;
|
32
|
+
string name = 2;
|
33
|
+
}
|
34
|
+
```
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# Schema: [id(integer), name(string)]
|
38
|
+
class User < ActiveRecord::Base
|
27
39
|
end
|
40
|
+
```
|
28
41
|
|
29
|
-
|
30
|
-
|
42
|
+
Implements a PbSerializer for the `User` message defined in `.proto`.
|
43
|
+
You need to declare the generated class and all defined fields in the PbSerializer.
|
31
44
|
|
32
|
-
|
33
|
-
|
34
|
-
|
45
|
+
```ruby
|
46
|
+
class UserPbSerializer < Pb::Serializer::Base
|
47
|
+
message ExamplesPb::User
|
35
48
|
|
36
49
|
attribute :id
|
37
|
-
attribute :
|
38
|
-
attribute :body
|
50
|
+
attribute :name
|
39
51
|
end
|
52
|
+
```
|
40
53
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
# @param req [YourApp::ListFriendUsersRequest]
|
50
|
-
# @param call [GRPC::ActiveCall::SingleReqView]
|
51
|
-
# @return [YourApp::ListFriendUsersResponse]
|
52
|
-
def list_friend_users(req, call)
|
53
|
-
current_user = User.find(current_user_id)
|
54
|
-
YourApp::ListFriendUsersResponse.new(
|
55
|
-
users: UserSerializer.bulk_load_and_serialize(ids: current_user.friend_ids, with: req.field_mask)
|
56
|
-
)
|
57
|
-
end
|
58
|
-
end
|
54
|
+
You can serialize Ruby objects to protobuf message object with the implemented PbSerializer.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
user = User.find(123)
|
58
|
+
UserPbSerializer.new(user).to_pb
|
59
|
+
# => <ExamplesPb::User: id: 123, name: "someuser">
|
59
60
|
```
|
60
61
|
|
61
|
-
|
62
|
+
The value of each attribute is determined from the PbSerializer instance or the object passed to the constructor.
|
63
|
+
|
64
|
+
## Next read
|
62
65
|
|
66
|
+
- [Examples](./docs/examples.md)
|
63
67
|
|
64
68
|
## Installation
|
65
69
|
|
@@ -77,10 +81,6 @@ Or install it yourself as:
|
|
77
81
|
|
78
82
|
$ gem install pb-serializer
|
79
83
|
|
80
|
-
## Usage
|
81
|
-
|
82
|
-
TODO: Write usage instructions here
|
83
|
-
|
84
84
|
## Development
|
85
85
|
|
86
86
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/docs/examples.md
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
# Examples
|
2
|
+
|
3
|
+
## Sub messages
|
4
|
+
|
5
|
+
```proto
|
6
|
+
message Post {
|
7
|
+
uint64 id = 1;
|
8
|
+
string title = 2;
|
9
|
+
User author = 3;
|
10
|
+
}
|
11
|
+
|
12
|
+
message User {
|
13
|
+
uint64 id = 1;
|
14
|
+
string name = 2;
|
15
|
+
}
|
16
|
+
```
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
# Schema: [id(integer), title(string), author_id(integer)]
|
20
|
+
class Book < ActiveRecord::Base
|
21
|
+
belongs_to :author, class_name: 'User'
|
22
|
+
end
|
23
|
+
|
24
|
+
# Schema: [id(integer), name(string)]
|
25
|
+
class User < ActiveRecord::Base
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class BookPbSerializer < Pb::Serializer::Base
|
31
|
+
message ExamplesPb::Book
|
32
|
+
|
33
|
+
attribute :id
|
34
|
+
attribute :title
|
35
|
+
attribute :author, serializer: UserPbSerializer
|
36
|
+
end
|
37
|
+
|
38
|
+
class UserPbSerializer < Pb::Serializer::Base
|
39
|
+
message ExamplesPb::User
|
40
|
+
|
41
|
+
attribute :id
|
42
|
+
attribute :name
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
## Enum
|
47
|
+
|
48
|
+
```proto
|
49
|
+
message Conversation {
|
50
|
+
uint64 id = 1;
|
51
|
+
Status status = 3;
|
52
|
+
|
53
|
+
enum Status {
|
54
|
+
STATUS_UNSPECIFIED = 0;
|
55
|
+
ARCHIVED = 1;
|
56
|
+
ACTIVE = 2;
|
57
|
+
}
|
58
|
+
}
|
59
|
+
```
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
# https://api.rubyonrails.org/classes/ActiveRecord/Enum.html
|
63
|
+
|
64
|
+
# Schema: [id(integer), status(integer)]
|
65
|
+
class Conversation < ApplicationRecord
|
66
|
+
enum status: { active: 0, archived: 1 }, _prefix: true
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# @!attribute [r] object
|
72
|
+
# @return [Conversation]
|
73
|
+
class ConversationPbSerializer < Pb::Serializer::Base
|
74
|
+
message ExamplesPb::Conversation
|
75
|
+
|
76
|
+
attribute :status
|
77
|
+
|
78
|
+
def status
|
79
|
+
object.status.upcase.to_sym
|
80
|
+
end
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
## Oneof
|
85
|
+
|
86
|
+
```proto
|
87
|
+
message Entry {
|
88
|
+
oneof entry {
|
89
|
+
Message message = 1;
|
90
|
+
Comment comment = 2;
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
message Message {
|
95
|
+
// ...
|
96
|
+
}
|
97
|
+
|
98
|
+
message Comment {
|
99
|
+
// ...
|
100
|
+
}
|
101
|
+
```
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
# see https://api.rubyonrails.org/classes/ActiveRecord/DelegatedType.html
|
105
|
+
|
106
|
+
class Entry < ApplicationRecord
|
107
|
+
delegated_type :entryable, types: %w[Message Comment]
|
108
|
+
end
|
109
|
+
|
110
|
+
class Message < ApplicationRecord
|
111
|
+
# ...
|
112
|
+
end
|
113
|
+
|
114
|
+
class Comment < ApplicationRecord
|
115
|
+
# ...
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
# @!attribute [r] object
|
121
|
+
# @return [Entry]
|
122
|
+
class EntryPbSerializer < Pb::Serializer::Base
|
123
|
+
message ExamplesPb::Entry
|
124
|
+
|
125
|
+
oneof :entry do
|
126
|
+
attribute :message, if: -> { object.message? }, serializer: MessagePbSerializer
|
127
|
+
attribute :comment, if: -> { object.comment? }, serializer: CommentPbSerializer
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# @!attribute [r] object
|
132
|
+
# @return [Message]
|
133
|
+
class MessagePbSerializer < Pb::Serializer::Base
|
134
|
+
message ExamplesPb::Message
|
135
|
+
|
136
|
+
# ...
|
137
|
+
end
|
138
|
+
|
139
|
+
# @!attribute [r] object
|
140
|
+
# @return [Comment]
|
141
|
+
class CommentPbSerializer < Pb::Serializer::Base
|
142
|
+
message ExamplesPb::Comment
|
143
|
+
|
144
|
+
# ...
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
## Serializable model
|
149
|
+
|
150
|
+
```proto
|
151
|
+
message User {
|
152
|
+
uint64 id = 1;
|
153
|
+
string first_name = 2;
|
154
|
+
string last_name = 3;
|
155
|
+
}
|
156
|
+
```
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
# Schema: [id(integer), first_name(string), last_name(string)]
|
160
|
+
class User < ActiveRecord::Base
|
161
|
+
include Pb::Serializable
|
162
|
+
|
163
|
+
message ExamplesPb::User
|
164
|
+
|
165
|
+
attribute :id
|
166
|
+
attribute :first_name
|
167
|
+
attribute :last_name
|
168
|
+
end
|
169
|
+
```
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
User.find(123).to_pb
|
173
|
+
# => <ExamplesPb::User: id: 123, first_name: 'Masayuki', last_name: 'Izumi'>
|
174
|
+
```
|
175
|
+
|
176
|
+
## With FieldMask and ComputedModel
|
177
|
+
|
178
|
+
```proto
|
179
|
+
message User {
|
180
|
+
uint64 id = 1;
|
181
|
+
string first_name = 2;
|
182
|
+
string last_name = 3;
|
183
|
+
string full_name = 4;
|
184
|
+
}
|
185
|
+
```
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
# Schema: [id(integer), first_name(string), last_name(string)]
|
189
|
+
class RawUser < ActiveRecord::Base
|
190
|
+
self.table_name = 'users'
|
191
|
+
end
|
192
|
+
|
193
|
+
class User
|
194
|
+
include ComputedModel::Model
|
195
|
+
|
196
|
+
def initialize(raw_user)
|
197
|
+
@raw_user = user
|
198
|
+
end
|
199
|
+
|
200
|
+
def self.batch_get(ids, with:)
|
201
|
+
bulk_load_and_compute([*Array(with), :id], ids: ids)
|
202
|
+
end
|
203
|
+
|
204
|
+
define_primary_loader :raw_user do |subfields, ids:, **|
|
205
|
+
RawUser.where(id: ids).select(subfields).map { new(_1) }
|
206
|
+
end
|
207
|
+
|
208
|
+
delegate_dependency :id, :first_name, :last_name,
|
209
|
+
to: :raw_user, include_subfields: true
|
210
|
+
|
211
|
+
dependency :first_name, :last_name
|
212
|
+
computed def full_name
|
213
|
+
[first_name, last_name].compact.join(' ')
|
214
|
+
end
|
215
|
+
end
|
216
|
+
```
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
class UserPbSerializer < Pb::Serializer::Base
|
220
|
+
message ExamplesPb::User
|
221
|
+
|
222
|
+
attribute :id
|
223
|
+
attribute :first_name
|
224
|
+
attribute :last_name
|
225
|
+
attribute :full_name
|
226
|
+
end
|
227
|
+
```
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
# req.read_mask # => <Google::Protobuf::FieldMask: paths: ['id', 'full_name']>
|
231
|
+
mask = Pb::Serializer.parse_field_mask(req.read_mask)
|
232
|
+
|
233
|
+
user = User.batch_get([123], with: mask)[0]
|
234
|
+
UserPbSerializer.new(user).to_pb(with: mask)
|
235
|
+
# => <ExamplesPb::User: id: 123, first_name: '', last_name: '', full_name: "Masayuki Izumi">
|
236
|
+
```
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Pb
|
2
|
+
module Serializable
|
3
|
+
# @private
|
4
|
+
module ComputedModelSupport
|
5
|
+
def self.included(base)
|
6
|
+
base.singleton_class.prepend Hook
|
7
|
+
end
|
8
|
+
|
9
|
+
private def primary_object
|
10
|
+
primary_object_name = self.class.__pb_serializer_primary_model_name
|
11
|
+
if primary_object_name
|
12
|
+
send(primary_object_name)
|
13
|
+
elsif kind_of?(Serializer::Base)
|
14
|
+
send(:object)
|
15
|
+
else
|
16
|
+
self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module Hook
|
21
|
+
attr_accessor :__pb_serializer_primary_model_name
|
22
|
+
|
23
|
+
def define_primary_loader(name)
|
24
|
+
self.__pb_serializer_primary_model_name = name
|
25
|
+
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def computed(name)
|
30
|
+
__pb_serializer_attrs << name
|
31
|
+
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
def define_loader(name, **)
|
36
|
+
__pb_serializer_attrs << name
|
37
|
+
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param with [Array]
|
42
|
+
private def __pb_serializer_filter_only_computed_model_attrs(with)
|
43
|
+
with.reject { |c| (__pb_serializer_attrs & (c.kind_of?(Hash) ? c.keys : [c])).empty? }
|
44
|
+
end
|
45
|
+
|
46
|
+
private def __pb_serializer_attrs
|
47
|
+
@__pb_serializer_attrs ||= Set.new
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Pb
|
2
|
+
module Serializable
|
3
|
+
module Dsl
|
4
|
+
# @api private
|
5
|
+
class Attribute < Struct.new(
|
6
|
+
:name,
|
7
|
+
:options,
|
8
|
+
:field_descriptor,
|
9
|
+
:oneof,
|
10
|
+
keyword_init: true,
|
11
|
+
)
|
12
|
+
|
13
|
+
ALLOWED_OPTIONS = Set[:allow_nil, :if, :serializer, :ignore].freeze
|
14
|
+
|
15
|
+
def initialize(options:, **)
|
16
|
+
super
|
17
|
+
|
18
|
+
unknown_options = options.keys.to_set - ALLOWED_OPTIONS
|
19
|
+
unless unknown_options.empty?
|
20
|
+
raise ::Pb::Serializer::InvalidAttributeOptionError, "unknown options are specified in #{name} attribute: #{unknown_options.to_a}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Boolean]
|
25
|
+
def allow_nil?
|
26
|
+
options.fetch(:allow_nil, false)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Class]
|
30
|
+
def serializer_class
|
31
|
+
options[:serializer]
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Boolean]
|
35
|
+
def repeated?
|
36
|
+
field_descriptor.label == :repeated
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Boolean]
|
40
|
+
def serializable?(s)
|
41
|
+
return false if options[:ignore]
|
42
|
+
|
43
|
+
cond = options[:if]
|
44
|
+
|
45
|
+
return true unless cond
|
46
|
+
|
47
|
+
case cond
|
48
|
+
when String, Symbol; then s.send(cond)
|
49
|
+
when Proc; then s.instance_exec(&cond)
|
50
|
+
else raise ::Pb::Serializer::InvalidAttributeOptionError, "`if` option can accept only Symbol, String or Proc. but got #{cond.class}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def oneof?
|
55
|
+
!oneof.nil?
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param v [Object]
|
59
|
+
# @param with [Hash, Array]
|
60
|
+
def convert_to_pb(v, with: nil, should_repeat: repeated?)
|
61
|
+
return nil if v.nil?
|
62
|
+
return v.map { |i| convert_to_pb(i, should_repeat: false, with: with) } if should_repeat
|
63
|
+
|
64
|
+
case field_descriptor.type
|
65
|
+
when :message
|
66
|
+
if v.class < Google::Protobuf::MessageExts && v.class.descriptor.name == field_descriptor.submsg_name
|
67
|
+
return v
|
68
|
+
end
|
69
|
+
|
70
|
+
case field_descriptor.submsg_name
|
71
|
+
when "google.protobuf.Timestamp" then Pb.to_timestamp(v)
|
72
|
+
when "google.protobuf.StringValue" then Pb.to_strval(v)
|
73
|
+
when "google.protobuf.Int32Value" then Pb.to_int32val(v)
|
74
|
+
when "google.protobuf.Int64Value" then Pb.to_int64val(v)
|
75
|
+
when "google.protobuf.UInt32Value" then Pb.to_uint32val(v)
|
76
|
+
when "google.protobuf.UInt64Value" then Pb.to_uint64val(v)
|
77
|
+
when "google.protobuf.FloatValue" then Pb.to_floatval(v)
|
78
|
+
when "google.protobuf.DoubleValue" then Pb.to_doubleval(v)
|
79
|
+
when "google.protobuf.BoolValue" then Pb.to_boolval(v)
|
80
|
+
when "google.protobuf.BytesValue" then Pb.to_bytesval(v)
|
81
|
+
else
|
82
|
+
return serializer_class.new(v).to_pb(with: with) if serializer_class
|
83
|
+
return v.to_pb(with: with) if v.kind_of?(::Pb::Serializable)
|
84
|
+
|
85
|
+
raise "serializer was not found for #{field_descriptor.submsg_name}"
|
86
|
+
end
|
87
|
+
else
|
88
|
+
v.nil? ? field_descriptor.default : v
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'pb/serializable/dsl/attribute'
|
2
|
+
require 'pb/serializable/dsl/oneof'
|
3
|
+
|
4
|
+
module Pb
|
5
|
+
module Serializable
|
6
|
+
module Dsl
|
7
|
+
# @param klass [Class] Protobuf message class
|
8
|
+
# @return [void]
|
9
|
+
def message(klass)
|
10
|
+
self.__pb_serializer_message_class = klass
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param name [Symbol] An attribute name
|
14
|
+
# @param opts [Hash] options
|
15
|
+
# @option opts [Boolean] :allow_nil Set true if this attribute allow to be nil
|
16
|
+
# @option opts [Class] :serializer A serializer class for this attribute
|
17
|
+
# @option opts [String, Symbol, Proc] :if A method, proc or string to call to determine to serialize this field
|
18
|
+
# @return [void]
|
19
|
+
# @raise [Pb::Serializer::MissingMessageTypeError] if this class has not been called {#message}
|
20
|
+
# @raise [Pb::Serializer::UnknownFieldError] if the field does not defined in .proto
|
21
|
+
# @raise [Pb::Serializer::InvalidAttributeOptionError] if unknown options are passed
|
22
|
+
def attribute(name, opts = {})
|
23
|
+
raise ::Pb::Serializer::MissingMessageTypeError, "message specificaiton is missed" unless __pb_serializer_message_class
|
24
|
+
|
25
|
+
fd = __pb_serializer_message_class.descriptor.find { |fd| fd.name.to_sym == name }
|
26
|
+
|
27
|
+
raise ::Pb::Serializer::UnknownFieldError, "#{name} is not defined in #{ __pb_serializer_message_class.name}" unless fd
|
28
|
+
|
29
|
+
attr = Attribute.new(
|
30
|
+
name: name,
|
31
|
+
options: opts,
|
32
|
+
field_descriptor: fd,
|
33
|
+
oneof: @current_oneof&.name,
|
34
|
+
)
|
35
|
+
|
36
|
+
__pb_serializer_attr_by_name[name] = attr
|
37
|
+
|
38
|
+
unless method_defined?(attr.name)
|
39
|
+
define_method attr.name do
|
40
|
+
primary_object.public_send(attr.name)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param names [Array<Symbol>] Attribute names to be ignored
|
46
|
+
# @return [void]
|
47
|
+
# @example Ignore attributes
|
48
|
+
# ignore :deprecated_field, :not_implemented_field
|
49
|
+
def ignore(*names)
|
50
|
+
names.each do |name|
|
51
|
+
attribute name, ignore: true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param name [Symbol] An oneof attribute name
|
56
|
+
# @param allow_nil [Boolean] Set true if this oneof attribute allow to be nil
|
57
|
+
# @return [void]
|
58
|
+
# @example Define oneof attributes
|
59
|
+
# oneof :test_oneof do
|
60
|
+
# attribute :name
|
61
|
+
# attribute :sub_message
|
62
|
+
# end
|
63
|
+
def oneof(name, allow_nil: false)
|
64
|
+
@current_oneof = Oneof.new(
|
65
|
+
name: name,
|
66
|
+
allow_nil: allow_nil,
|
67
|
+
attributes: [],
|
68
|
+
)
|
69
|
+
yield
|
70
|
+
__pb_serializer_oneof_by_name[name] = @current_oneof
|
71
|
+
@current_oneof = nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/pb/serializable.rb
CHANGED
@@ -1,30 +1,39 @@
|
|
1
|
+
require "pb/serializable/computed_model_support"
|
2
|
+
require "pb/serializable/dsl"
|
3
|
+
|
1
4
|
module Pb
|
2
5
|
module Serializable
|
3
6
|
extend ActiveSupport::Concern
|
4
7
|
include ComputedModel::Model
|
8
|
+
|
9
|
+
# @!parse extend Dsl
|
10
|
+
# @!parse extend ClassMethods
|
11
|
+
|
5
12
|
def self.included(base)
|
6
|
-
base.
|
7
|
-
base.
|
13
|
+
base.include ComputedModelSupport
|
14
|
+
base.extend Dsl
|
8
15
|
end
|
9
16
|
|
10
17
|
# @param with [
|
11
18
|
# Google::Protobuf::FieldMask,
|
12
19
|
# Array<(Symbol, Hash)>,
|
13
|
-
# Hash{Symbol=>(Array,Symbol,Hash)},
|
14
|
-
#
|
15
|
-
#
|
20
|
+
# Hash{Symbol=>(Array,Symbol,Hash,Proc)},
|
21
|
+
# ]
|
22
|
+
# Specifies the list of fields to be serialized in the Proto message object.
|
23
|
+
# `nil` means that all fields defined in .proto will be serialized.
|
24
|
+
# @return [Object] a protobuf message object
|
16
25
|
def to_pb(with: nil)
|
17
|
-
with ||= ::Pb::Serializer.build_default_mask(self.class.
|
18
|
-
with = ::Pb::Serializer
|
26
|
+
with ||= ::Pb::Serializer.build_default_mask(self.class.__pb_serializer_message_class.descriptor)
|
27
|
+
with = ::Pb::Serializer.normalize_mask(with)
|
19
28
|
|
20
29
|
oneof_set = []
|
21
30
|
|
22
|
-
o = self.class.
|
23
|
-
self.class.
|
24
|
-
attr = self.class.
|
31
|
+
o = self.class.__pb_serializer_message_class.new
|
32
|
+
self.class.__pb_serializer_message_class.descriptor.each do |fd|
|
33
|
+
attr = self.class.__pb_serializer_attr_by_field_descriptor(fd)
|
25
34
|
|
26
35
|
unless attr
|
27
|
-
msg = "#{self.class.
|
36
|
+
msg = "#{self.class.__pb_serializer_message_class.name}.#{fd.name} is missed in #{self.class.name}"
|
28
37
|
|
29
38
|
case Pb::Serializer.configuration.missing_field_behavior
|
30
39
|
when :raise then raise ::Pb::Serializer::MissingFieldError, msg
|
@@ -62,7 +71,7 @@ module Pb
|
|
62
71
|
end
|
63
72
|
end
|
64
73
|
|
65
|
-
self.class.
|
74
|
+
self.class.__pb_serializer_oneof_by_name.values.each do |oneof|
|
66
75
|
next if oneof_set.include?(oneof.name)
|
67
76
|
next if oneof.allow_nil?
|
68
77
|
raise ::Pb::Serializer::ValidationError, "#{primary_object.class.name}##{oneof.name} is required"
|
@@ -71,79 +80,7 @@ module Pb
|
|
71
80
|
o
|
72
81
|
end
|
73
82
|
|
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
|
-
|
105
83
|
module ClassMethods
|
106
|
-
attr_reader :message_class
|
107
|
-
attr_accessor :__pb_serializer_primary_model_name
|
108
|
-
|
109
|
-
def message(klass)
|
110
|
-
@message_class = klass
|
111
|
-
end
|
112
|
-
|
113
|
-
# @param name [Symbol] An attribute name
|
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
|
-
|
121
|
-
fd = message_class.descriptor.find { |fd| fd.name.to_sym == name }
|
122
|
-
|
123
|
-
raise ::Pb::Serializer::UnknownFieldError, "#{name} is not defined in #{message_class.name}" unless fd
|
124
|
-
|
125
|
-
attr = ::Pb::Serializer::Attribute.new(
|
126
|
-
name: name,
|
127
|
-
options: opts,
|
128
|
-
field_descriptor: fd,
|
129
|
-
oneof: @current_oneof&.name,
|
130
|
-
)
|
131
|
-
|
132
|
-
@attr_by_name ||= {}
|
133
|
-
@attr_by_name[name] = attr
|
134
|
-
|
135
|
-
define_method attr.name do
|
136
|
-
primary_object.public_send(attr.name)
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
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
|
145
|
-
end
|
146
|
-
|
147
84
|
# @param with [Array, Hash, Google::Protobuf::FieldMask, nil]
|
148
85
|
# @return [Array]
|
149
86
|
def bulk_load_and_serialize(with: nil, **args)
|
@@ -151,9 +88,9 @@ module Pb
|
|
151
88
|
end
|
152
89
|
|
153
90
|
def bulk_load(with: nil, **args)
|
154
|
-
with ||= ::Pb::Serializer.build_default_mask(
|
155
|
-
with = ::Pb::Serializer
|
156
|
-
with = with
|
91
|
+
with ||= ::Pb::Serializer.build_default_mask(__pb_serializer_message_class.descriptor)
|
92
|
+
with = ::Pb::Serializer.normalize_mask(with)
|
93
|
+
with = __pb_serializer_filter_only_computed_model_attrs(with)
|
157
94
|
|
158
95
|
primary_object_name = __pb_serializer_primary_model_name
|
159
96
|
if primary_object_name
|
@@ -165,30 +102,26 @@ module Pb
|
|
165
102
|
bulk_load_and_compute(with, **args)
|
166
103
|
end
|
167
104
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
yield
|
176
|
-
@oneof_by_name[name] = @current_oneof
|
177
|
-
@current_oneof = nil
|
105
|
+
# @api private
|
106
|
+
attr_accessor :__pb_serializer_message_class
|
107
|
+
|
108
|
+
# @api private
|
109
|
+
# @return [Hash{Symbol=>::Pb::Serializer::Attribute}]
|
110
|
+
def __pb_serializer_attr_by_name
|
111
|
+
@__pb_serializer_attr_by_name ||= {}
|
178
112
|
end
|
179
113
|
|
180
|
-
|
181
|
-
|
114
|
+
# @api private
|
115
|
+
# @return [Hash{Symbol=>::Pb::Serializer::Oneof}]
|
116
|
+
def __pb_serializer_oneof_by_name
|
117
|
+
@__pb_serializer_oneof_by_name ||= {}
|
182
118
|
end
|
183
119
|
|
120
|
+
# @api private
|
184
121
|
# @param fd [Google::Protobuf::FieldDescriptor] a field descriptor
|
185
122
|
# @return [Pb::Serializer::Attribute, nil]
|
186
|
-
def
|
187
|
-
|
188
|
-
end
|
189
|
-
|
190
|
-
def oneofs
|
191
|
-
@oneof_by_name&.values || []
|
123
|
+
def __pb_serializer_attr_by_field_descriptor(fd)
|
124
|
+
__pb_serializer_attr_by_name[fd.name.to_sym]
|
192
125
|
end
|
193
126
|
end
|
194
127
|
end
|
data/lib/pb/serializer/base.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module Pb
|
2
2
|
module Serializer
|
3
3
|
class Base
|
4
|
+
# @!parse include Pb::Serializable
|
5
|
+
|
4
6
|
def self.inherited(base)
|
5
7
|
base.include ::Pb::Serializable
|
6
8
|
base.singleton_class.prepend Hook
|
@@ -12,6 +14,7 @@ module Pb
|
|
12
14
|
@object = object
|
13
15
|
end
|
14
16
|
|
17
|
+
# @private
|
15
18
|
module Hook
|
16
19
|
def define_primary_loader(name, &block)
|
17
20
|
class_eval <<~RUBY
|
data/lib/pb/serializer.rb
CHANGED
@@ -4,10 +4,7 @@ require "computed_model"
|
|
4
4
|
require "google/protobuf/field_mask_pb"
|
5
5
|
|
6
6
|
require "pb/serializable"
|
7
|
-
require "pb/serializer/normalized_mask"
|
8
7
|
require "pb/serializer/base"
|
9
|
-
require "pb/serializer/attribute"
|
10
|
-
require "pb/serializer/oneof"
|
11
8
|
|
12
9
|
module Pb
|
13
10
|
module Serializer
|
@@ -34,6 +31,7 @@ module Pb
|
|
34
31
|
end
|
35
32
|
|
36
33
|
# @param v [:raise, :warn, :ignore]
|
34
|
+
# @return [void]
|
37
35
|
def missing_field_behavior=(v)
|
38
36
|
@missing_field_behavior = v
|
39
37
|
|
@@ -50,6 +48,7 @@ module Pb
|
|
50
48
|
# end
|
51
49
|
# @yield [c]
|
52
50
|
# @yieldparam [Configuration] config
|
51
|
+
# @return [void]
|
53
52
|
def configure
|
54
53
|
yield configuration
|
55
54
|
end
|
@@ -64,7 +63,7 @@ module Pb
|
|
64
63
|
configuration.logger
|
65
64
|
end
|
66
65
|
|
67
|
-
# @param [Google::Protobuf::Descriptor]
|
66
|
+
# @param descriptor [Google::Protobuf::Descriptor]
|
68
67
|
def build_default_mask(descriptor)
|
69
68
|
set =
|
70
69
|
descriptor.each_with_object(Set[]) do |fd, m|
|
@@ -82,7 +81,7 @@ module Pb
|
|
82
81
|
"google.protobuf.BoolValue" ,
|
83
82
|
"google.protobuf.BytesValue" then m << fd.name.to_sym
|
84
83
|
else
|
85
|
-
m << { fd.name.to_sym => build_default_mask(fd.subtype) }
|
84
|
+
m << { fd.name.to_sym => -> { build_default_mask(fd.subtype) } }
|
86
85
|
end
|
87
86
|
else
|
88
87
|
m << fd.name.to_sym
|
@@ -90,6 +89,48 @@ module Pb
|
|
90
89
|
end
|
91
90
|
set.to_a
|
92
91
|
end
|
92
|
+
|
93
|
+
# @param field_mask [Google::Protobuf::FieldMask]
|
94
|
+
# @return [Array]
|
95
|
+
def parse_field_mask(field_mask)
|
96
|
+
unless field_mask.kind_of?(Google::Protobuf::FieldMask)
|
97
|
+
raise ArgumentError, "expected Google::Protobuf::FieldMask, but got #{field_mask.class}"
|
98
|
+
end
|
99
|
+
|
100
|
+
field_mask.paths.map do |path|
|
101
|
+
path.split(".").reverse.inject(nil) { |h, key| h.nil? ? key.to_sym : { key.to_sym => [h].compact } }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# @param input [Google::Protobuf::FieldMask, Symbol, Array<(Symbol,Hash)>, Hash{Symbol=>(Array,Symbol,Hash,Proc)}, Proc]
|
106
|
+
# @return [Hash{Symbol=>(Array,Hash,Proc)}]
|
107
|
+
def normalize_mask(input)
|
108
|
+
if input.kind_of?(Google::Protobuf::FieldMask)
|
109
|
+
input = parse_field_mask(input)
|
110
|
+
end
|
111
|
+
|
112
|
+
input = input.call if input.kind_of?(Proc)
|
113
|
+
input = [input] if input.kind_of?(Hash)
|
114
|
+
|
115
|
+
normalized = {}
|
116
|
+
Array(input).each do |el|
|
117
|
+
case el
|
118
|
+
when Symbol
|
119
|
+
normalized[el] ||= []
|
120
|
+
when Hash
|
121
|
+
el.each do |k, v|
|
122
|
+
v = v.call if v.kind_of?(Proc)
|
123
|
+
v = [v] if v.kind_of?(Hash)
|
124
|
+
normalized[k] ||= []
|
125
|
+
normalized[k].push(*Array(v))
|
126
|
+
end
|
127
|
+
else
|
128
|
+
raise "not supported field mask type: #{input.class}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
normalized
|
133
|
+
end
|
93
134
|
end
|
94
135
|
end
|
95
136
|
end
|
data/pb-serializer.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
|
11
11
|
spec.summary = "Serialize objects into Protocol Buffers messages"
|
12
12
|
spec.description = spec.summary
|
13
|
-
spec.homepage = "https://github.com/
|
13
|
+
spec.homepage = "https://github.com/wantedly/pb-serializer"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.metadata["homepage_uri"] = spec.homepage
|
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_development_dependency "activerecord", rails_versions
|
35
35
|
spec.add_development_dependency "bundler", "~> 2.0"
|
36
36
|
spec.add_development_dependency "onkcop", "~> 0.53"
|
37
|
-
spec.add_development_dependency "rake", "~>
|
37
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
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"
|
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.5.
|
4
|
+
version: 0.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- izumin5210
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: google-protobuf
|
@@ -106,14 +106,14 @@ dependencies:
|
|
106
106
|
requirements:
|
107
107
|
- - "~>"
|
108
108
|
- !ruby/object:Gem::Version
|
109
|
-
version: '
|
109
|
+
version: '13.0'
|
110
110
|
type: :development
|
111
111
|
prerelease: false
|
112
112
|
version_requirements: !ruby/object:Gem::Requirement
|
113
113
|
requirements:
|
114
114
|
- - "~>"
|
115
115
|
- !ruby/object:Gem::Version
|
116
|
-
version: '
|
116
|
+
version: '13.0'
|
117
117
|
- !ruby/object:Gem::Dependency
|
118
118
|
name: rspec
|
119
119
|
requirement: !ruby/object:Gem::Requirement
|
@@ -197,30 +197,34 @@ files:
|
|
197
197
|
- ".rspec"
|
198
198
|
- ".rubocop.yml"
|
199
199
|
- ".rubocop_todo.yml"
|
200
|
+
- ".yardopts"
|
200
201
|
- CHANGELOG.md
|
201
202
|
- CODE_OF_CONDUCT.md
|
202
203
|
- Gemfile
|
203
204
|
- LICENSE.txt
|
205
|
+
- README.ja.md
|
204
206
|
- README.md
|
205
207
|
- Rakefile
|
206
208
|
- bin/console
|
207
209
|
- bin/setup
|
208
210
|
- codecov.yml
|
211
|
+
- docs/examples.md
|
209
212
|
- lib/pb/serializable.rb
|
213
|
+
- lib/pb/serializable/computed_model_support.rb
|
214
|
+
- lib/pb/serializable/dsl.rb
|
215
|
+
- lib/pb/serializable/dsl/attribute.rb
|
216
|
+
- lib/pb/serializable/dsl/oneof.rb
|
210
217
|
- lib/pb/serializer.rb
|
211
|
-
- lib/pb/serializer/attribute.rb
|
212
218
|
- lib/pb/serializer/base.rb
|
213
|
-
- lib/pb/serializer/normalized_mask.rb
|
214
|
-
- lib/pb/serializer/oneof.rb
|
215
219
|
- lib/pb/serializer/version.rb
|
216
220
|
- pb-serializer.gemspec
|
217
|
-
homepage: https://github.com/
|
221
|
+
homepage: https://github.com/wantedly/pb-serializer
|
218
222
|
licenses:
|
219
223
|
- MIT
|
220
224
|
metadata:
|
221
|
-
homepage_uri: https://github.com/
|
222
|
-
source_code_uri: https://github.com/
|
223
|
-
changelog_uri: https://github.com/
|
225
|
+
homepage_uri: https://github.com/wantedly/pb-serializer
|
226
|
+
source_code_uri: https://github.com/wantedly/pb-serializer
|
227
|
+
changelog_uri: https://github.com/wantedly/pb-serializer
|
224
228
|
post_install_message:
|
225
229
|
rdoc_options: []
|
226
230
|
require_paths:
|
@@ -236,7 +240,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
236
240
|
- !ruby/object:Gem::Version
|
237
241
|
version: '0'
|
238
242
|
requirements: []
|
239
|
-
rubygems_version: 3.2.
|
243
|
+
rubygems_version: 3.2.33
|
240
244
|
signing_key:
|
241
245
|
specification_version: 4
|
242
246
|
summary: Serialize objects into Protocol Buffers messages
|
@@ -1,91 +0,0 @@
|
|
1
|
-
module Pb
|
2
|
-
module Serializer
|
3
|
-
class Attribute < Struct.new(
|
4
|
-
:name,
|
5
|
-
:options,
|
6
|
-
:field_descriptor,
|
7
|
-
:oneof,
|
8
|
-
keyword_init: true,
|
9
|
-
)
|
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
|
-
|
22
|
-
# @return [Boolean]
|
23
|
-
def allow_nil?
|
24
|
-
options.fetch(:allow_nil, false)
|
25
|
-
end
|
26
|
-
|
27
|
-
# @return [Class]
|
28
|
-
def serializer_class
|
29
|
-
options[:serializer]
|
30
|
-
end
|
31
|
-
|
32
|
-
# @return [Boolean]
|
33
|
-
def repeated?
|
34
|
-
field_descriptor.label == :repeated
|
35
|
-
end
|
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
|
-
|
52
|
-
def oneof?
|
53
|
-
!oneof.nil?
|
54
|
-
end
|
55
|
-
|
56
|
-
# @param v [Object]
|
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
|
61
|
-
|
62
|
-
case field_descriptor.type
|
63
|
-
when :message
|
64
|
-
if v.class < Google::Protobuf::MessageExts && v.class.descriptor.name == field_descriptor.submsg_name
|
65
|
-
return v
|
66
|
-
end
|
67
|
-
|
68
|
-
case field_descriptor.submsg_name
|
69
|
-
when "google.protobuf.Timestamp" then Pb.to_timestamp(v)
|
70
|
-
when "google.protobuf.StringValue" then Pb.to_strval(v)
|
71
|
-
when "google.protobuf.Int32Value" then Pb.to_int32val(v)
|
72
|
-
when "google.protobuf.Int64Value" then Pb.to_int64val(v)
|
73
|
-
when "google.protobuf.UInt32Value" then Pb.to_uint32val(v)
|
74
|
-
when "google.protobuf.UInt64Value" then Pb.to_uint64val(v)
|
75
|
-
when "google.protobuf.FloatValue" then Pb.to_floatval(v)
|
76
|
-
when "google.protobuf.DoubleValue" then Pb.to_doubleval(v)
|
77
|
-
when "google.protobuf.BoolValue" then Pb.to_boolval(v)
|
78
|
-
when "google.protobuf.BytesValue" then Pb.to_bytesval(v)
|
79
|
-
else
|
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)
|
82
|
-
|
83
|
-
raise "serializer was not found for #{field_descriptor.submsg_name}"
|
84
|
-
end
|
85
|
-
else
|
86
|
-
v.nil? ? field_descriptor.default : v
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
@@ -1,57 +0,0 @@
|
|
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
|