swarmclient 0.1.3 → 0.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 78b0081e269fca254447d15622cf08ecfa779e17
4
- data.tar.gz: '001971af13049e326c9c8bd9096bd5cb40b496a7'
3
+ metadata.gz: 99f80ba44aa24f9ee3eb4e9ce141e3ecf63e3f94
4
+ data.tar.gz: 4d428173cc0aa564f57f4022ad50d5f72308764b
5
5
  SHA512:
6
- metadata.gz: 8021ab7bb29ed5791e95d9850c79cd445a517fd63bdf7ad1673d5e2d8d2a79cf3ebab766098861ee5c51330a494a2242a96aa06714a0581b0f9dddb1e3952e72
7
- data.tar.gz: a6eea143378cfb711ad48c80e4d090e7f03788260fbf9959484a01c5f790272492ccd0f3b68abf4ea0b38ec454ea160d15b9786cdcd3710d69aae2541caf79e0
6
+ metadata.gz: b330011864610edf645f0f2694783726f0247b5f9a79b31ff18f742a434d0b0a9d1f85417330aa5e6e56d41945c6d3c67c9237943a52f943b39f76a68c7a3fbb
7
+ data.tar.gz: e42427944c2c2f0b88574efe20f07eb9edadd567e3e7918c65db318a46f819641ee323578bbe02f0e48cf3ad7f3aa5fcc475f19767abe297eaec432e6f49c4ca
data/.gitignore CHANGED
@@ -7,6 +7,7 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  *.gem
10
+ .idea
10
11
 
11
12
  # rspec failure tracking
12
13
  .rspec_status
data/.gitmodules ADDED
@@ -0,0 +1,4 @@
1
+ [submodule "proto"]
2
+ path = proto
3
+ url = https://github.com/bluzelle/swarmdb
4
+ branch = devel
data/.travis.yml CHANGED
@@ -1,7 +1,23 @@
1
1
  sudo: false
2
2
  language: ruby
3
+
4
+ addons:
5
+ apt:
6
+ sources:
7
+ - ubuntu-toolchain-r-test
8
+ - sourceline: 'deb http://ppa.launchpad.net/maarten-fonville/protobuf/ubuntu trusty main'
9
+ packages:
10
+ - protobuf-compiler
11
+
12
+ before_install:
13
+ # Uncommented until issue is resolved: https://github.com/google/protobuf/issues/4852
14
+ # - mkdir -p lib/swarmclient/protobuf
15
+ # - protoc --ruby_out=lib/swarmclient/protobuf --proto_path=proto/proto bluzelle.proto database.proto audit.proto
16
+ - gem install bundler -v 1.16.0
17
+
18
+
3
19
  rvm:
4
20
  - 2.4.2
21
+
5
22
  script:
6
- - bundle exec rake spec
7
- before_install: gem install bundler -v 1.16.0
23
+ - bundle exec rake spec
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  [![Build Status](https://api.travis-ci.org/wlwanpan/swarmclient-rb.png?branch=master)](https://travis-ci.org/wlwanpan/swarmclient-rb)
4
4
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
5
+ [![Twitter](https://img.shields.io/badge/twitter-@bluzelle-blue.svg?style=flat-square)](https://twitter.com/BluzelleHQ)
6
+ [![Gitter chat](https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=flat-square)](https://gitter.im/bluzelle)
5
7
 
6
8
  ## Installation
7
9
 
@@ -25,68 +27,83 @@ Or build and install from src:
25
27
  ## Communication API (Swarmclient::Communication)
26
28
 
27
29
  Require and Initialize
28
- ```
30
+ ```ruby
29
31
  require 'swarmclient'
30
32
 
31
33
  bluzelle = Swarmclient::Communication.new endpoint: '127.0.0.1', port: 51010, uuid: '80174b53-2dda-49f1-9d6a-6a780d4'
32
34
  ```
33
35
 
34
36
  Note: The uuid is the unique id of a referenced db hosted in the swarm.
35
- Generate a new one to generate a new store. The gem will default to:
36
- '8c073d96-7291-11e8-adc0-fa7ae01bbebc' if none is provided.
37
- Refer to https://bluzelle.github.io/api/ for more info.
37
+ Generate a new one to generate a new database. The gem will default to:
38
+ "8c073d96-7291-11e8-adc0-fa7ae01bbebc" if none is provided.
38
39
 
39
40
  Create New Entry (key-value)
40
- ```
41
+ ```ruby
41
42
  bluzelle.create 'myKey', 'Your Value'
42
43
  ```
44
+ - Result
45
+ ```ruby
46
+ => true
47
+ ```
43
48
 
44
49
  Read Key
45
- ```
50
+ ```ruby
46
51
  bluzelle.read 'myKey'
47
52
  ```
48
53
  - Result
49
- ```
54
+ ```ruby
50
55
  => "Your Value"
51
56
  ```
52
57
 
53
58
  Update Key value
54
- ```
59
+ ```ruby
55
60
  bluzelle.update 'myKey', 'New Value'
56
61
  ```
62
+ - Result
63
+ ```ruby
64
+ => true
65
+ ```
57
66
 
58
67
  Remove Key
59
- ```
68
+ ```ruby
60
69
  bluzelle.remove 'myKey'
61
70
  ```
71
+ - Result
72
+ ```ruby
73
+ => true
74
+ ```
62
75
 
63
76
  Check if key exist
64
- ```
77
+ ```ruby
65
78
  bluzelle.has 'myKey'
66
79
  ```
67
80
  - Result
68
- ```
81
+ ```ruby
69
82
  => true
70
83
  ```
71
84
 
72
85
  Read all keys stored
73
- ```
86
+ ```ruby
74
87
  bluzelle.keys
75
88
  ```
76
89
  - Result
77
- ```
78
- => ["myKey1"]
90
+ ```ruby
91
+ => ["myKey"]
79
92
  ```
80
93
 
81
94
  Get size of database
82
- ```
95
+ ```ruby
83
96
  bluzelle.size
84
97
  ```
85
98
  - Result
86
- ```
99
+ ```ruby
87
100
  => 1
88
101
  ```
89
102
 
103
+ ## Reference
104
+
105
+ Visit the official bluzelle [documentation](https://bluzelle.github.io/api/)
106
+
90
107
  ## Development
91
108
 
92
109
  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.
@@ -1,23 +1,15 @@
1
- require 'google/protobuf'
2
1
  require 'base64'
3
- require 'websocket-client-simple'
4
- require 'eventmachine'
5
2
  require 'json'
6
3
 
7
- require_relative './protobuf/bluzelle_pb'
8
- require_relative './protobuf/database_pb'
9
-
10
- DEFAULT_UUID = '8c073d96-7291-11e8-adc0-fa7ae01bbebc'
11
- DEFAULT_IP = '13.78.131.94' # '127.0.0.1'
12
- DEFAULT_PORT = 51010 # 8100
4
+ require_relative './constants'
5
+ require_relative './proto_serializer'
6
+ require_relative './connection'
13
7
 
14
8
  module Swarmclient
15
9
  class Communication
16
-
17
- attr_accessor :transaction_id_limit, :ws_set_timeout
18
-
19
- @transaction_id_limit = 100
20
- @ws_set_timeout = 5
10
+ include Constants
11
+ include ProtoSerializer
12
+ include Connection
21
13
 
22
14
  def initialize endpoint: DEFAULT_IP, port: DEFAULT_PORT, uuid: DEFAULT_UUID, secure: false
23
15
 
@@ -25,138 +17,88 @@ module Swarmclient
25
17
  @_port = port
26
18
  @_uuid = uuid
27
19
  @_protocol_prefix = secure ? 'wss://' : 'ws://'
20
+ @_redirect_attempt = 0
28
21
 
29
22
  end
30
23
 
31
24
  def create key, value
32
- send cmd: 'create', data: { key: key, value: value.to_s }
25
+ send_request cmd: 'create', data: { key: key, value: value.to_s }
33
26
  end
34
27
 
35
28
  def read key
36
- send cmd: 'read', data: { key: key }
29
+ send_request cmd: 'read', data: { key: key }
37
30
  end
38
31
 
39
32
  def update key, value
40
- send cmd: 'update', data: { key: key, value: value.to_s }
33
+ send_request cmd: 'update', data: { key: key, value: value.to_s }
41
34
  end
42
35
 
43
36
  def remove key
44
- send cmd: 'delete', data: { key: key }
37
+ send_request cmd: 'delete', data: { key: key }
45
38
  end
46
39
 
47
40
  def has key
48
- send cmd: 'has', data: { key: key }
41
+ send_request cmd: 'has', data: { key: key }
49
42
  end
50
43
 
51
44
  def keys
52
- send cmd: 'keys', data: nil
45
+ send_request cmd: 'keys', data: nil
53
46
  end
54
47
 
55
48
  def size
56
- send cmd: 'size', data: nil
49
+ send_request cmd: 'size', data: nil
57
50
  end
58
51
 
59
52
  private
60
53
 
61
- def encoded_protobuf_msg cmd:, protobuf_cmd_data:
62
- db_msg = Database_msg.new
63
- db_msg.header = Database_header.new db_uuid: @_uuid, transaction_id: rand(@transaction_id_limit).to_i
64
- db_msg[cmd] = protobuf_cmd_data
65
- bzn_msg = Bzn_msg.new db: db_msg
66
- Bzn_msg.encode bzn_msg
67
- end
68
-
69
- def generate_req cmd:, data:
70
- protobuf_cmd = cmd_to_protobuf cmd
71
- protobuf_cmd_msg = data.nil? ? protobuf_cmd.new : protobuf_cmd.new(data)
72
-
73
- encoded_msg = encoded_protobuf_msg cmd: cmd, protobuf_cmd_data: protobuf_cmd_msg
54
+ def generate_req **options
55
+ db_msg = generate_db_msg options.merge db_uuid: @_uuid
56
+ encoded_msg = encode_msg db_msg
74
57
  encoded64_msg = Base64.strict_encode64 encoded_msg
75
58
 
76
59
  {"bzn-api": "database","msg": encoded64_msg}.to_json
77
60
  end
78
61
 
79
- def cmd_to_protobuf cmd
80
- processed_cmd =
81
- case cmd
82
- when 'keys', 'size' then 'empty'
83
- else cmd
84
- end
85
-
86
- Object.const_get "Database_#{processed_cmd}"
87
- end
88
-
89
62
  def generate_endpoint
90
63
  [@_protocol_prefix, @_endpoint, ':', @_port.to_s].join('')
91
64
  end
92
65
 
93
- def send cmd:, data:
94
- endpoint, req = [
95
- generate_endpoint,
96
- generate_req({ cmd: cmd, data: data })
97
- ]
66
+ def send_request **options
67
+ raise StandardError.new "Max Leader redirect attempt reached" if @_redirect_attempt >= MAX_REDIRECT_ATTEMPT
98
68
 
99
- err, res = get req: req, endpoint: endpoint
100
- return err if err
101
- raise 'No Response' if res.nil?
69
+ endpoint = generate_endpoint
70
+ req = generate_req options
102
71
 
103
- db_response = Database_response.decode res
72
+ err, res = connect_and_send req: req, endpoint: endpoint
73
+ raise err unless err.nil?
74
+
75
+ db_response = decode_res res
104
76
 
105
77
  if db_response.redirect
106
78
  puts 'Switching leader_host: ' + db_response.redirect.leader_name
79
+ @_redirect_attempt += 1
107
80
  @_endpoint, @_port = [
108
81
  db_response.redirect.leader_host,
109
- db_response.redirect.leader_port
82
+ db_response.redirect.leader_port,
110
83
  ]
111
84
 
112
- return send cmd: cmd, data: data
85
+ return send_request options
113
86
 
114
- elsif !db_response.resp.nil? && !db_response.resp.error.empty?
115
- return db_response.resp.error
87
+ elsif !(db_response.resp.nil? || db_response.resp.error.empty?)
88
+ raise db_response.resp.error
116
89
 
117
90
  else
118
- case cmd
119
- when 'create', 'update', 'delete' then nil
120
- when 'read' then db_response.resp.value
121
- else db_response.resp[cmd]
122
- end
123
-
124
- end
125
-
126
- end
127
-
128
- def get req:, endpoint:
129
- res, err = [nil, nil]
130
-
131
- begin
132
- EventMachine.run do
133
- ws = WebSocket::Client::Simple.connect endpoint
134
-
135
- ws.on :message do |msg|
136
- res = msg.data
137
- EventMachine::stop_event_loop
138
- end
139
-
140
- ws.on :open do
141
- ws.send req
142
- end
143
-
144
- ws.on :close do |e|
145
- EventMachine::stop_event_loop
146
- end
147
-
148
- ws.on :error do |e|
149
- err ||= e
150
- EventMachine::stop_event_loop
151
- end
152
-
153
- EventMachine::Timer.new(5) { ws.close }
91
+ @_redirect_attempt = 0
92
+ case options[:cmd]
93
+ when 'create', 'update', 'delete' then true
94
+ when 'read' then db_response.resp.value
95
+ else db_response.resp[options[:cmd]]
154
96
  end
155
- rescue => e
156
- err = e
157
97
  end
158
98
 
159
- return [err, res]
99
+ rescue => e
100
+ @_redirect_attempt = 0
101
+ e.message
160
102
  end
161
103
 
162
104
  end
@@ -0,0 +1,46 @@
1
+ require 'websocket-client-simple'
2
+ require 'eventmachine'
3
+
4
+ require_relative './constants'
5
+
6
+ module Swarmclient
7
+ module Connection
8
+ include Constants
9
+
10
+ private
11
+
12
+ def connect_and_send req:, endpoint:
13
+ res, err = [nil, nil]
14
+
15
+ begin
16
+ EventMachine.run do
17
+ ws = WebSocket::Client::Simple.connect endpoint
18
+
19
+ ws.on :message do |msg|
20
+ res = msg.data
21
+ EventMachine::stop_event_loop
22
+ end
23
+
24
+ ws.on :open do
25
+ ws.send req
26
+ end
27
+
28
+ ws.on :close do |e|
29
+ EventMachine::stop_event_loop
30
+ end
31
+
32
+ ws.on :error do |e|
33
+ err ||= e
34
+ EventMachine::stop_event_loop
35
+ end
36
+
37
+ EventMachine::Timer.new(CONNECTION_TIMEOUT_LIMIT) { ws.close }
38
+ end
39
+ rescue => e
40
+ err = e
41
+ end
42
+
43
+ return [err, res]
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,32 @@
1
+ module Swarmclient
2
+ module Constants
3
+
4
+ ##
5
+ # Randomly generated uuid, will add validation if not initialize in the future.
6
+ DEFAULT_UUID = '8c073d96-7291-11e8-adc0-fa7ae01bbebc'
7
+
8
+ ##
9
+ # Points default ip to localhost.
10
+ DEFAULT_IP = '127.0.0.1'
11
+
12
+ ##
13
+ # Points to default swarmDB port
14
+ # For more details: https://github.com/bluzelle/swarmDB
15
+ DEFAULT_PORT = 51010
16
+
17
+ ##
18
+ # Set limit in seconds per socket before raising a Timeout Error
19
+ CONNECTION_TIMEOUT_LIMIT = 3
20
+
21
+ ##
22
+ # Temporary redirect attempt limit.
23
+ # Track discussion on: https://gitter.im/bluzelle/opensource
24
+ MAX_REDIRECT_ATTEMPT = 3
25
+
26
+ ##
27
+ # Not yet confirmed the randomness of db_msg.header.transaction_id
28
+ # https://github.com/wlwanpan/swarmclient-rb/issues/4
29
+ TRANSATION_ID_UPPER_LIMIT = 1000
30
+
31
+ end
32
+ end
@@ -0,0 +1,45 @@
1
+ require 'google/protobuf'
2
+
3
+ require_relative './constants'
4
+ require_relative './protobuf/bluzelle_pb'
5
+ require_relative './protobuf/database_pb'
6
+
7
+ module Swarmclient
8
+ module ProtoSerializer
9
+ include Constants
10
+
11
+ private
12
+
13
+ def decode_res res
14
+ Database_response.decode res
15
+ end
16
+
17
+ def encode_msg msg
18
+ bzn_msg = Bzn_msg.new db: msg
19
+ Bzn_msg.encode bzn_msg
20
+ end
21
+
22
+ def generate_db_msg cmd:, data:, db_uuid:
23
+ protobuf_cmd = cmd_to_protobuf cmd
24
+ protobuf_cmd_msg = data.nil? ? protobuf_cmd.new : protobuf_cmd.new(data)
25
+
26
+ proto_header_msg = Database_header.new db_uuid: db_uuid, transaction_id: rand(TRANSATION_ID_UPPER_LIMIT).to_i
27
+
28
+ db_msg = Database_msg.new
29
+ db_msg.header = proto_header_msg
30
+ db_msg[cmd] = protobuf_cmd_msg
31
+ db_msg
32
+ end
33
+
34
+ def cmd_to_protobuf cmd
35
+ processed_cmd =
36
+ case cmd
37
+ when 'keys', 'size' then 'empty'
38
+ else cmd
39
+ end
40
+
41
+ Object.const_get "Database_#{processed_cmd}"
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,28 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: audit.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ Google::Protobuf::DescriptorPool.generated_pool.build do
7
+ add_message "audit_message" do
8
+ oneof :msg do
9
+ optional :commit, :message, 1, "commit_notification"
10
+ optional :leader_status, :message, 2, "leader_status"
11
+ end
12
+ end
13
+ add_message "leader_status" do
14
+ optional :term, :uint64, 1
15
+ optional :leader, :string, 2
16
+ optional :current_log_index, :uint64, 3
17
+ optional :current_commit_index, :uint64, 4
18
+ end
19
+ add_message "commit_notification" do
20
+ optional :sender_uuid, :string, 1
21
+ optional :log_index, :uint64, 2
22
+ optional :operation, :string, 3
23
+ end
24
+ end
25
+
26
+ Audit_message = Google::Protobuf::DescriptorPool.generated_pool.lookup("audit_message").msgclass
27
+ Leader_status = Google::Protobuf::DescriptorPool.generated_pool.lookup("leader_status").msgclass
28
+ Commit_notification = Google::Protobuf::DescriptorPool.generated_pool.lookup("commit_notification").msgclass
@@ -4,12 +4,13 @@
4
4
  require 'google/protobuf'
5
5
 
6
6
  require_relative './database_pb'
7
-
7
+ require_relative './audit_pb'
8
8
  Google::Protobuf::DescriptorPool.generated_pool.build do
9
9
  add_message "bzn_msg" do
10
10
  oneof :msg do
11
11
  optional :db, :message, 10, "database_msg"
12
12
  optional :json, :string, 11
13
+ optional :audit_message, :message, 12, "audit_message"
13
14
  end
14
15
  end
15
16
  end
@@ -1,3 +1,3 @@
1
1
  module Swarmclient
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
data/swarmclient.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["Warren"]
10
10
  spec.email = ["wlwanpan@uwaterloo.ca"]
11
11
 
12
- spec.summary = %q{A gem for the bluzele SwarmDB}
12
+ spec.summary = %q{A gem for the CRUD operations on the bluzelle SwarmDB}
13
13
  # spec.description = %q{TODO: Write a longer description or delete this line.}
14
14
  spec.homepage = "https://github.com/wlwanpan/swarmclient-rb"
15
15
  spec.license = "MIT"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swarmclient
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Warren
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-06-29 00:00:00.000000000 Z
11
+ date: 2018-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -116,6 +116,7 @@ extensions: []
116
116
  extra_rdoc_files: []
117
117
  files:
118
118
  - ".gitignore"
119
+ - ".gitmodules"
119
120
  - ".rspec"
120
121
  - ".travis.yml"
121
122
  - CODE_OF_CONDUCT.md
@@ -127,12 +128,14 @@ files:
127
128
  - bin/setup
128
129
  - lib/swarmclient.rb
129
130
  - lib/swarmclient/communication.rb
131
+ - lib/swarmclient/connection.rb
132
+ - lib/swarmclient/constants.rb
133
+ - lib/swarmclient/proto_serializer.rb
134
+ - lib/swarmclient/protobuf/audit_pb.rb
130
135
  - lib/swarmclient/protobuf/bluzelle_pb.rb
131
136
  - lib/swarmclient/protobuf/database_pb.rb
132
137
  - lib/swarmclient/pubsub.rb
133
138
  - lib/swarmclient/version.rb
134
- - proto/bluzelle.proto
135
- - proto/database.proto
136
139
  - swarmclient.gemspec
137
140
  homepage: https://github.com/wlwanpan/swarmclient-rb
138
141
  licenses:
@@ -157,5 +160,5 @@ rubyforge_project:
157
160
  rubygems_version: 2.6.14
158
161
  signing_key:
159
162
  specification_version: 4
160
- summary: A gem for the bluzele SwarmDB
163
+ summary: A gem for the CRUD operations on the bluzelle SwarmDB
161
164
  test_files: []
data/proto/bluzelle.proto DELETED
@@ -1,14 +0,0 @@
1
- syntax = "proto3";
2
-
3
- // Bluzelle message definition from:
4
- // https://github.com/bluzelle/swarmDB/blob/devel/proto/bluzelle.proto
5
-
6
- import "database.proto";
7
-
8
- message bzn_msg
9
- {
10
- oneof msg {
11
- database_msg db = 10;
12
- string json = 11;
13
- }
14
- }
data/proto/database.proto DELETED
@@ -1,83 +0,0 @@
1
- syntax = "proto3";
2
-
3
- // Database message definition from:
4
- // https://github.com/bluzelle/swarmDB/blob/devel/proto/database.proto
5
-
6
- message database_msg
7
- {
8
- database_header header = 2;
9
-
10
- oneof msg {
11
- database_create create = 10;
12
- database_read read = 11;
13
- database_update update = 12;
14
- database_delete delete = 13;
15
- database_has has = 14;
16
- database_empty keys = 15;
17
- database_empty size = 16;
18
- }
19
- }
20
-
21
- message database_redirect_response
22
- {
23
- string leader_id = 1;
24
- string leader_name = 2;
25
- string leader_host = 3;
26
- uint32 leader_port = 4;
27
- uint32 leader_http_port = 5;
28
- }
29
-
30
- message database_header
31
- {
32
- string db_uuid = 1;
33
- uint64 transaction_id = 2;
34
- }
35
-
36
- message database_create
37
- {
38
- string key = 2;
39
- bytes value = 3;
40
- }
41
-
42
- message database_read
43
- {
44
- string key = 2;
45
- }
46
-
47
- message database_update
48
- {
49
- string key = 2;
50
- bytes value = 3;
51
- }
52
-
53
- message database_delete
54
- {
55
- string key = 2;
56
- }
57
-
58
- message database_has
59
- {
60
- string key = 2;
61
- }
62
-
63
- message database_empty {}
64
-
65
- message database_response
66
- {
67
- database_header header = 1;
68
-
69
- oneof success
70
- {
71
- database_redirect_response redirect = 2;
72
- response resp = 3;
73
- }
74
-
75
- message response
76
- {
77
- bytes value = 4;
78
- bool has = 5;
79
- int32 size = 6;
80
- string error = 7;
81
- repeated string keys = 8;
82
- }
83
- }