pb-serializer 0.5.1 → 0.6.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 +3 -4
- data/.yardopts +8 -0
- data/CHANGELOG.md +15 -7
- data/README.ja.md +66 -0
- data/README.md +46 -46
- data/docs/examples.md +236 -0
- data/lib/pb/{serializer → serializable}/computed_model_support.rb +2 -1
- 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 +42 -11
- data/lib/pb/serializer/base.rb +3 -0
- data/lib/pb/serializer/version.rb +1 -1
- data/lib/pb/serializer.rb +14 -14
- data/pb-serializer.gemspec +5 -4
- metadata +34 -17
- data/lib/pb/serializer/attribute.rb +0 -91
- data/lib/pb/serializer/dsl.rb +0 -69
- 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: 9af1a5991c847f416646d4d15bae84654e0c7ef8e7882d7dac3bf6c05b2eaf89
|
|
4
|
+
data.tar.gz: 774ee2f7de5eb0a4a9e16cbea7392d63e0586d453728191d2bd85be7bbf91a95
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8033c801f4e94b1dccb22245f4460926c30c29f762be71e68243c75fc24c50f67c258ea10fb30d24ddde120fb1d36498a0e85aac44b80be8493b682fa8d969b6
|
|
7
|
+
data.tar.gz: 5295c8ae6f9c580edf34c96dc13d8b37f3920d999d440d0003efa885ee7b165ef938a1ed0df3e822de279f33b611a283de20455e0f7d78baa3b4c5d85447c80d
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -9,10 +9,9 @@ jobs:
|
|
|
9
9
|
fail-fast: false
|
|
10
10
|
matrix:
|
|
11
11
|
os: [ ubuntu-latest, macos-latest ]
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
ruby: [ 2.5, 2.6, 2.7 ]
|
|
12
|
+
ruby: [ '3.0', '3.1', '3.2', '3.3' ]
|
|
13
|
+
exclude:
|
|
14
|
+
- { os: macos-latest, ruby: '2.5' }
|
|
16
15
|
runs-on: ${{ matrix.os }}
|
|
17
16
|
|
|
18
17
|
steps:
|
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
## Unreleased
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
- Change default value of `missing_field_behavior` from `:raise` to `:ignore` https://github.com/wantedly/pb-serializer/pull/56
|
|
6
|
+
- Update CI Ruby versions to 3.0+ https://github.com/wantedly/pb-serializer/pull/56
|
|
7
|
+
- Update `simplecov-cobertura` to latest version https://github.com/wantedly/pb-serializer/pull/56
|
|
8
|
+
|
|
9
|
+
## 0.5.2
|
|
10
|
+
|
|
11
|
+
- Generate the default mask lazily to prevent infinite recursions https://github.com/wantedly/pb-serializer/pull/52
|
|
12
|
+
|
|
3
13
|
## 0.5.1
|
|
4
14
|
|
|
5
15
|
- Improving interoperability with `computed_model`
|
|
@@ -22,15 +32,14 @@
|
|
|
22
32
|
|
|
23
33
|
- Support `if` option https://github.com/wantedly/pb-serializer/pull/24
|
|
24
34
|
- Improve error handling https://github.com/wantedly/pb-serializer/pull/26
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
- raise `MissingMessageTypeError` if `message` declaration is missed
|
|
36
|
+
- raise `MissingFieldError` if `attribute` declaration is missed
|
|
37
|
+
- raise `InvalidOptionError` when `attribute` receives invalid params
|
|
28
38
|
- Introduce Pb::Serializer.configure https://github.com/wantedly/pb-serializer/pull/27
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
- Add `missing_field_behavior` config to suppress `MissingFieldError`
|
|
40
|
+
- Rename `InvalidOptionError` -> `InvalidAttributeOptionError`
|
|
31
41
|
- Skip serializing when a value is already serialized https://github.com/wantedly/pb-serializer/pull/29
|
|
32
42
|
|
|
33
|
-
|
|
34
43
|
## 0.2.1
|
|
35
44
|
|
|
36
45
|
- **BREAKING CHANGE** `required` -> `allow_nil` https://github.com/wantedly/pb-serializer/pull/21
|
|
@@ -44,7 +53,6 @@
|
|
|
44
53
|
- Change API
|
|
45
54
|
- Add example specs https://github.com/wantedly/pb-serializer/pull/18
|
|
46
55
|
|
|
47
|
-
|
|
48
56
|
## 0.1.0
|
|
49
57
|
|
|
50
58
|
Initial release.
|
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,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
|
|
5
8
|
|
|
9
|
+
# @!parse extend Dsl
|
|
10
|
+
# @!parse extend ClassMethods
|
|
11
|
+
|
|
6
12
|
def self.included(base)
|
|
7
|
-
base.include
|
|
8
|
-
base.extend
|
|
13
|
+
base.include ComputedModelSupport
|
|
14
|
+
base.extend Dsl
|
|
9
15
|
end
|
|
10
16
|
|
|
11
17
|
# @param with [
|
|
12
18
|
# Google::Protobuf::FieldMask,
|
|
13
19
|
# Array<(Symbol, Hash)>,
|
|
14
|
-
# Hash{Symbol=>(Array,Symbol,Hash)},
|
|
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.
|
|
26
|
+
with ||= ::Pb::Serializer.build_default_mask(self.class.__pb_serializer_message_class.descriptor)
|
|
18
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"
|
|
@@ -79,7 +88,7 @@ module Pb
|
|
|
79
88
|
end
|
|
80
89
|
|
|
81
90
|
def bulk_load(with: nil, **args)
|
|
82
|
-
with ||= ::Pb::Serializer.build_default_mask(
|
|
91
|
+
with ||= ::Pb::Serializer.build_default_mask(__pb_serializer_message_class.descriptor)
|
|
83
92
|
with = ::Pb::Serializer.normalize_mask(with)
|
|
84
93
|
with = __pb_serializer_filter_only_computed_model_attrs(with)
|
|
85
94
|
|
|
@@ -92,6 +101,28 @@ module Pb
|
|
|
92
101
|
|
|
93
102
|
bulk_load_and_compute(with, **args)
|
|
94
103
|
end
|
|
104
|
+
|
|
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 ||= {}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# @api private
|
|
115
|
+
# @return [Hash{Symbol=>::Pb::Serializer::Oneof}]
|
|
116
|
+
def __pb_serializer_oneof_by_name
|
|
117
|
+
@__pb_serializer_oneof_by_name ||= {}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @api private
|
|
121
|
+
# @param fd [Google::Protobuf::FieldDescriptor] a field descriptor
|
|
122
|
+
# @return [Pb::Serializer::Attribute, nil]
|
|
123
|
+
def __pb_serializer_attr_by_field_descriptor(fd)
|
|
124
|
+
__pb_serializer_attr_by_name[fd.name.to_sym]
|
|
125
|
+
end
|
|
95
126
|
end
|
|
96
127
|
end
|
|
97
128
|
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
|
@@ -3,12 +3,8 @@ require "the_pb"
|
|
|
3
3
|
require "computed_model"
|
|
4
4
|
require "google/protobuf/field_mask_pb"
|
|
5
5
|
|
|
6
|
-
require "pb/serializer/dsl"
|
|
7
|
-
require "pb/serializer/computed_model_support"
|
|
8
6
|
require "pb/serializable"
|
|
9
7
|
require "pb/serializer/base"
|
|
10
|
-
require "pb/serializer/attribute"
|
|
11
|
-
require "pb/serializer/oneof"
|
|
12
8
|
|
|
13
9
|
module Pb
|
|
14
10
|
module Serializer
|
|
@@ -26,15 +22,16 @@ module Pb
|
|
|
26
22
|
# @return [Logger]
|
|
27
23
|
attr_accessor :logger
|
|
28
24
|
# @!attribute [r] missing_field_behavior
|
|
29
|
-
# @return [:raise, :warn, :ignore] default: `:
|
|
25
|
+
# @return [:raise, :warn, :ignore] default: `:ignore`
|
|
30
26
|
attr_reader :missing_field_behavior
|
|
31
27
|
|
|
32
28
|
def initialize
|
|
33
|
-
self.missing_field_behavior = :
|
|
29
|
+
self.missing_field_behavior = :ignore
|
|
34
30
|
self.logger = Logger.new(STDOUT)
|
|
35
31
|
end
|
|
36
32
|
|
|
37
33
|
# @param v [:raise, :warn, :ignore]
|
|
34
|
+
# @return [void]
|
|
38
35
|
def missing_field_behavior=(v)
|
|
39
36
|
@missing_field_behavior = v
|
|
40
37
|
|
|
@@ -51,6 +48,7 @@ module Pb
|
|
|
51
48
|
# end
|
|
52
49
|
# @yield [c]
|
|
53
50
|
# @yieldparam [Configuration] config
|
|
51
|
+
# @return [void]
|
|
54
52
|
def configure
|
|
55
53
|
yield configuration
|
|
56
54
|
end
|
|
@@ -65,14 +63,14 @@ module Pb
|
|
|
65
63
|
configuration.logger
|
|
66
64
|
end
|
|
67
65
|
|
|
68
|
-
# @param [Google::Protobuf::Descriptor]
|
|
66
|
+
# @param descriptor [Google::Protobuf::Descriptor]
|
|
69
67
|
def build_default_mask(descriptor)
|
|
70
68
|
set =
|
|
71
69
|
descriptor.each_with_object(Set[]) do |fd, m|
|
|
72
70
|
case fd.type
|
|
73
71
|
when :message
|
|
74
72
|
case fd.submsg_name
|
|
75
|
-
when "google.protobuf.Timestamp",
|
|
73
|
+
when "google.protobuf.Timestamp",
|
|
76
74
|
"google.protobuf.StringValue",
|
|
77
75
|
"google.protobuf.Int32Value" ,
|
|
78
76
|
"google.protobuf.Int64Value" ,
|
|
@@ -83,7 +81,7 @@ module Pb
|
|
|
83
81
|
"google.protobuf.BoolValue" ,
|
|
84
82
|
"google.protobuf.BytesValue" then m << fd.name.to_sym
|
|
85
83
|
else
|
|
86
|
-
m << { fd.name.to_sym => build_default_mask(fd.subtype) }
|
|
84
|
+
m << { fd.name.to_sym => -> { build_default_mask(fd.subtype) } }
|
|
87
85
|
end
|
|
88
86
|
else
|
|
89
87
|
m << fd.name.to_sym
|
|
@@ -92,7 +90,7 @@ module Pb
|
|
|
92
90
|
set.to_a
|
|
93
91
|
end
|
|
94
92
|
|
|
95
|
-
# @param [Google::Protobuf::FieldMask]
|
|
93
|
+
# @param field_mask [Google::Protobuf::FieldMask]
|
|
96
94
|
# @return [Array]
|
|
97
95
|
def parse_field_mask(field_mask)
|
|
98
96
|
unless field_mask.kind_of?(Google::Protobuf::FieldMask)
|
|
@@ -104,22 +102,24 @@ module Pb
|
|
|
104
102
|
end
|
|
105
103
|
end
|
|
106
104
|
|
|
107
|
-
# @param [Google::Protobuf::FieldMask, Symbol, Array<(Symbol,Hash)>, Hash{Symbol=>(Array,Symbol,Hash)}]
|
|
108
|
-
# @return [Hash{Symbol=>(Array,Hash)}]
|
|
105
|
+
# @param input [Google::Protobuf::FieldMask, Symbol, Array<(Symbol,Hash)>, Hash{Symbol=>(Array,Symbol,Hash,Proc)}, Proc]
|
|
106
|
+
# @return [Hash{Symbol=>(Array,Hash,Proc)}]
|
|
109
107
|
def normalize_mask(input)
|
|
110
108
|
if input.kind_of?(Google::Protobuf::FieldMask)
|
|
111
109
|
input = parse_field_mask(input)
|
|
112
110
|
end
|
|
113
111
|
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
input = input.call if input.kind_of?(Proc)
|
|
116
113
|
input = [input] if input.kind_of?(Hash)
|
|
114
|
+
|
|
115
|
+
normalized = {}
|
|
117
116
|
Array(input).each do |el|
|
|
118
117
|
case el
|
|
119
118
|
when Symbol
|
|
120
119
|
normalized[el] ||= []
|
|
121
120
|
when Hash
|
|
122
121
|
el.each do |k, v|
|
|
122
|
+
v = v.call if v.kind_of?(Proc)
|
|
123
123
|
v = [v] if v.kind_of?(Hash)
|
|
124
124
|
normalized[k] ||= []
|
|
125
125
|
normalized[k].push(*Array(v))
|
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,10 +34,11 @@ 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"
|
|
41
|
-
spec.add_development_dependency "simplecov", "~> 0.
|
|
42
|
-
spec.add_development_dependency "simplecov-cobertura", "~> 1
|
|
41
|
+
spec.add_development_dependency "simplecov", "~> 0.19"
|
|
42
|
+
spec.add_development_dependency "simplecov-cobertura", "~> 3.1"
|
|
43
|
+
spec.add_development_dependency "concurrent-ruby", "1.3.4"
|
|
43
44
|
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.6.0
|
|
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: 2025-11-13 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
|
|
@@ -162,28 +162,42 @@ dependencies:
|
|
|
162
162
|
requirements:
|
|
163
163
|
- - "~>"
|
|
164
164
|
- !ruby/object:Gem::Version
|
|
165
|
-
version: 0.
|
|
165
|
+
version: '0.19'
|
|
166
166
|
type: :development
|
|
167
167
|
prerelease: false
|
|
168
168
|
version_requirements: !ruby/object:Gem::Requirement
|
|
169
169
|
requirements:
|
|
170
170
|
- - "~>"
|
|
171
171
|
- !ruby/object:Gem::Version
|
|
172
|
-
version: 0.
|
|
172
|
+
version: '0.19'
|
|
173
173
|
- !ruby/object:Gem::Dependency
|
|
174
174
|
name: simplecov-cobertura
|
|
175
175
|
requirement: !ruby/object:Gem::Requirement
|
|
176
176
|
requirements:
|
|
177
177
|
- - "~>"
|
|
178
178
|
- !ruby/object:Gem::Version
|
|
179
|
-
version: '1
|
|
179
|
+
version: '3.1'
|
|
180
180
|
type: :development
|
|
181
181
|
prerelease: false
|
|
182
182
|
version_requirements: !ruby/object:Gem::Requirement
|
|
183
183
|
requirements:
|
|
184
184
|
- - "~>"
|
|
185
185
|
- !ruby/object:Gem::Version
|
|
186
|
-
version: '1
|
|
186
|
+
version: '3.1'
|
|
187
|
+
- !ruby/object:Gem::Dependency
|
|
188
|
+
name: concurrent-ruby
|
|
189
|
+
requirement: !ruby/object:Gem::Requirement
|
|
190
|
+
requirements:
|
|
191
|
+
- - '='
|
|
192
|
+
- !ruby/object:Gem::Version
|
|
193
|
+
version: 1.3.4
|
|
194
|
+
type: :development
|
|
195
|
+
prerelease: false
|
|
196
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
197
|
+
requirements:
|
|
198
|
+
- - '='
|
|
199
|
+
- !ruby/object:Gem::Version
|
|
200
|
+
version: 1.3.4
|
|
187
201
|
description: Serialize objects into Protocol Buffers messages
|
|
188
202
|
email:
|
|
189
203
|
- m@izum.in
|
|
@@ -197,31 +211,34 @@ files:
|
|
|
197
211
|
- ".rspec"
|
|
198
212
|
- ".rubocop.yml"
|
|
199
213
|
- ".rubocop_todo.yml"
|
|
214
|
+
- ".yardopts"
|
|
200
215
|
- CHANGELOG.md
|
|
201
216
|
- CODE_OF_CONDUCT.md
|
|
202
217
|
- Gemfile
|
|
203
218
|
- LICENSE.txt
|
|
219
|
+
- README.ja.md
|
|
204
220
|
- README.md
|
|
205
221
|
- Rakefile
|
|
206
222
|
- bin/console
|
|
207
223
|
- bin/setup
|
|
208
224
|
- codecov.yml
|
|
225
|
+
- docs/examples.md
|
|
209
226
|
- lib/pb/serializable.rb
|
|
227
|
+
- lib/pb/serializable/computed_model_support.rb
|
|
228
|
+
- lib/pb/serializable/dsl.rb
|
|
229
|
+
- lib/pb/serializable/dsl/attribute.rb
|
|
230
|
+
- lib/pb/serializable/dsl/oneof.rb
|
|
210
231
|
- lib/pb/serializer.rb
|
|
211
|
-
- lib/pb/serializer/attribute.rb
|
|
212
232
|
- lib/pb/serializer/base.rb
|
|
213
|
-
- lib/pb/serializer/computed_model_support.rb
|
|
214
|
-
- lib/pb/serializer/dsl.rb
|
|
215
|
-
- lib/pb/serializer/oneof.rb
|
|
216
233
|
- lib/pb/serializer/version.rb
|
|
217
234
|
- pb-serializer.gemspec
|
|
218
|
-
homepage: https://github.com/
|
|
235
|
+
homepage: https://github.com/wantedly/pb-serializer
|
|
219
236
|
licenses:
|
|
220
237
|
- MIT
|
|
221
238
|
metadata:
|
|
222
|
-
homepage_uri: https://github.com/
|
|
223
|
-
source_code_uri: https://github.com/
|
|
224
|
-
changelog_uri: https://github.com/
|
|
239
|
+
homepage_uri: https://github.com/wantedly/pb-serializer
|
|
240
|
+
source_code_uri: https://github.com/wantedly/pb-serializer
|
|
241
|
+
changelog_uri: https://github.com/wantedly/pb-serializer
|
|
225
242
|
post_install_message:
|
|
226
243
|
rdoc_options: []
|
|
227
244
|
require_paths:
|
|
@@ -237,7 +254,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
237
254
|
- !ruby/object:Gem::Version
|
|
238
255
|
version: '0'
|
|
239
256
|
requirements: []
|
|
240
|
-
rubygems_version: 3.
|
|
257
|
+
rubygems_version: 3.5.9
|
|
241
258
|
signing_key:
|
|
242
259
|
specification_version: 4
|
|
243
260
|
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 [Hash, Array]
|
|
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
|
data/lib/pb/serializer/dsl.rb
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
module Pb
|
|
2
|
-
module Serializer
|
|
3
|
-
module Dsl
|
|
4
|
-
def message(klass)
|
|
5
|
-
@message_class = klass
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
# @param name [Symbol] An attribute name
|
|
9
|
-
# @param [Hash] opts options
|
|
10
|
-
# @option opts [Boolean] :allow_nil Set true if this attribute allow to be nil
|
|
11
|
-
# @option opts [Class] :serializer A serializer class for this attribute
|
|
12
|
-
# @option opts [String, Symbol, Proc] :if A method, proc or string to call to determine to serialize this field
|
|
13
|
-
def attribute(name, opts = {})
|
|
14
|
-
raise ::Pb::Serializer::MissingMessageTypeError, "message specificaiton is missed" unless message_class
|
|
15
|
-
|
|
16
|
-
fd = message_class.descriptor.find { |fd| fd.name.to_sym == name }
|
|
17
|
-
|
|
18
|
-
raise ::Pb::Serializer::UnknownFieldError, "#{name} is not defined in #{message_class.name}" unless fd
|
|
19
|
-
|
|
20
|
-
attr = ::Pb::Serializer::Attribute.new(
|
|
21
|
-
name: name,
|
|
22
|
-
options: opts,
|
|
23
|
-
field_descriptor: fd,
|
|
24
|
-
oneof: @current_oneof&.name,
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
@attr_by_name ||= {}
|
|
28
|
-
@attr_by_name[name] = attr
|
|
29
|
-
|
|
30
|
-
unless method_defined?(attr.name)
|
|
31
|
-
define_method attr.name do
|
|
32
|
-
primary_object.public_send(attr.name)
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# @param names [Array<Symbol>] Attribute names to be ignored
|
|
38
|
-
def ignore(*names)
|
|
39
|
-
names.each do |name|
|
|
40
|
-
attribute name, ignore: true
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def oneof(name, allow_nil: false)
|
|
45
|
-
@oneof_by_name ||= {}
|
|
46
|
-
@current_oneof = ::Pb::Serializer::Oneof.new(
|
|
47
|
-
name: name,
|
|
48
|
-
allow_nil: allow_nil,
|
|
49
|
-
attributes: [],
|
|
50
|
-
)
|
|
51
|
-
yield
|
|
52
|
-
@oneof_by_name[name] = @current_oneof
|
|
53
|
-
@current_oneof = nil
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
attr_reader :message_class
|
|
57
|
-
|
|
58
|
-
# @param fd [Google::Protobuf::FieldDescriptor] a field descriptor
|
|
59
|
-
# @return [Pb::Serializer::Attribute, nil]
|
|
60
|
-
def find_attribute_by_field_descriptor(fd)
|
|
61
|
-
(@attr_by_name || {})[fd.name.to_sym]
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def oneofs
|
|
65
|
-
@oneof_by_name&.values || []
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|