messagepack 1.0.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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/README.adoc +773 -0
  3. data/Rakefile +8 -0
  4. data/docs/Gemfile +7 -0
  5. data/docs/README.md +85 -0
  6. data/docs/_config.yml +137 -0
  7. data/docs/_guides/index.adoc +14 -0
  8. data/docs/_guides/io-streaming.adoc +226 -0
  9. data/docs/_guides/migration.adoc +218 -0
  10. data/docs/_guides/performance.adoc +189 -0
  11. data/docs/_pages/buffer.adoc +85 -0
  12. data/docs/_pages/extension-types.adoc +117 -0
  13. data/docs/_pages/factory-pattern.adoc +115 -0
  14. data/docs/_pages/index.adoc +20 -0
  15. data/docs/_pages/serialization.adoc +159 -0
  16. data/docs/_pages/streaming.adoc +97 -0
  17. data/docs/_pages/symbol-extension.adoc +69 -0
  18. data/docs/_pages/timestamp-extension.adoc +88 -0
  19. data/docs/_references/api.adoc +360 -0
  20. data/docs/_references/extensions.adoc +198 -0
  21. data/docs/_references/format.adoc +301 -0
  22. data/docs/_references/index.adoc +14 -0
  23. data/docs/_tutorials/extension-types.adoc +170 -0
  24. data/docs/_tutorials/getting-started.adoc +165 -0
  25. data/docs/_tutorials/index.adoc +14 -0
  26. data/docs/_tutorials/thread-safety.adoc +157 -0
  27. data/docs/index.adoc +77 -0
  28. data/docs/lychee.toml +42 -0
  29. data/lib/messagepack/bigint.rb +131 -0
  30. data/lib/messagepack/buffer.rb +534 -0
  31. data/lib/messagepack/core_ext.rb +34 -0
  32. data/lib/messagepack/error.rb +24 -0
  33. data/lib/messagepack/extensions/base.rb +55 -0
  34. data/lib/messagepack/extensions/registry.rb +154 -0
  35. data/lib/messagepack/extensions/symbol.rb +38 -0
  36. data/lib/messagepack/extensions/timestamp.rb +110 -0
  37. data/lib/messagepack/extensions/value.rb +38 -0
  38. data/lib/messagepack/factory.rb +349 -0
  39. data/lib/messagepack/format.rb +99 -0
  40. data/lib/messagepack/packer.rb +702 -0
  41. data/lib/messagepack/symbol.rb +4 -0
  42. data/lib/messagepack/time.rb +29 -0
  43. data/lib/messagepack/timestamp.rb +4 -0
  44. data/lib/messagepack/unpacker.rb +1418 -0
  45. data/lib/messagepack/version.rb +5 -0
  46. data/lib/messagepack.rb +81 -0
  47. metadata +94 -0
@@ -0,0 +1,165 @@
1
+ ---
2
+ title: Getting started
3
+ nav_order: 1
4
+ ---
5
+
6
+ == Purpose
7
+
8
+ This tutorial introduces the basics of using MessagePack Ruby to serialize and
9
+ deserialize Ruby objects.
10
+
11
+ == References
12
+
13
+ * link:../pages/serialization[Serialization] - Core serialization concepts
14
+ * link:../references/api[API reference] - Complete API documentation
15
+
16
+ == Prerequisites
17
+
18
+ * Basic knowledge of Ruby
19
+ * Ruby 2.7 or higher
20
+
21
+ == Installing the gem
22
+
23
+ Add to your Gemfile:
24
+
25
+ [source,ruby]
26
+ ----
27
+ gem 'messagepack'
28
+ ----
29
+
30
+ Or install directly:
31
+
32
+ [source,shell]
33
+ ----
34
+ gem install messagepack
35
+ ----
36
+
37
+ == Your first serialization
38
+
39
+ === Basic pack and unpack
40
+
41
+ [source,ruby]
42
+ ----
43
+ require 'messagepack'
44
+
45
+ # Pack some data
46
+ data = { message: "Hello, MessagePack!" }
47
+ binary = Messagepack.pack(data)
48
+
49
+ # Inspect the binary (non-printable characters escaped)
50
+ puts binary.inspect
51
+ # => "\\x81\\xA7message\\xB4Hello, MessagePack!"
52
+
53
+ # Unpack the data
54
+ result = Messagepack.unpack(binary)
55
+ puts result.inspect
56
+ # => {"message"=>"Hello, MessagePack!"}
57
+ ----
58
+
59
+ === Working with different types
60
+
61
+ [source,ruby]
62
+ ----
63
+ # Numbers
64
+ Messagepack.pack(42) # Integer
65
+ Messagepack.pack(3.14) # Float
66
+ Messagepack.pack(-100) # Negative integer
67
+
68
+ # Strings and symbols
69
+ Messagepack.pack("hello") # String
70
+ Messagepack.pack(:world) # Symbol (serialized as string)
71
+
72
+ # Collections
73
+ Messagepack.pack([1, 2, 3]) # Array
74
+ Messagepack.pack({a: 1}) # Hash
75
+
76
+ # Special values
77
+ Messagepack.pack(nil) # Nil
78
+ Messagepack.pack(true) # Boolean true
79
+ Messagepack.pack(false) # Boolean false
80
+ ----
81
+
82
+ == Working with IO
83
+
84
+ === Writing to a file
85
+
86
+ [source,ruby]
87
+ ----
88
+ data = { name: "Alice", age: 30, skills: ["Ruby", "Python"] }
89
+
90
+ # Write to file
91
+ File.open("data.msgpack", "wb") do |file|
92
+ file.write(Messagepack.pack(data))
93
+ end
94
+ ----
95
+
96
+ === Reading from a file
97
+
98
+ [source,ruby]
99
+ ----
100
+ # Read from file
101
+ data = File.read("data.msgpack", encoding: Encoding::BINARY)
102
+ result = Messagepack.unpack(data)
103
+ # => {"name"=>"Alice", "age"=>30, "skills"=>["Ruby", "Python"]}
104
+ ----
105
+
106
+ === Using IO directly
107
+
108
+ [source,ruby]
109
+ ----
110
+ # Unpack can read from IO directly
111
+ File.open("data.msgpack", "rb") do |file|
112
+ result = Messagepack.unpack(file)
113
+ end
114
+ ----
115
+
116
+ == Practical example: Configuration file
117
+
118
+ Create a simple configuration system using MessagePack:
119
+
120
+ [source,ruby]
121
+ ----
122
+ require 'messagepack'
123
+
124
+ class Config
125
+ def initialize(path)
126
+ @path = path
127
+ @data = load
128
+ end
129
+
130
+ def [](key)
131
+ @data[key]
132
+ end
133
+
134
+ def []=(key, value)
135
+ @data[key] = value
136
+ save
137
+ end
138
+
139
+ private
140
+
141
+ def load
142
+ if File.exist?(@path)
143
+ Messagepack.unpack(File.read(@path, encoding: Encoding::BINARY))
144
+ else
145
+ {}
146
+ end
147
+ end
148
+
149
+ def save
150
+ File.write(@path, Messagepack.pack(@data))
151
+ end
152
+ end
153
+
154
+ # Usage
155
+ config = Config.new("config.msgpack")
156
+ config[:database] = { host: "localhost", port: 5432 }
157
+ config[:debug] = true
158
+
159
+ puts config[:database][:host] # => "localhost"
160
+ ----
161
+
162
+ == Next steps
163
+
164
+ * link:../tutorials/extension-types[Custom extension types] - Learn to serialize custom classes
165
+ * link:../guides/performance[Performance guide] - Optimize your serialization code
@@ -0,0 +1,14 @@
1
+ ---
2
+ title: Tutorials
3
+ nav_order: 1
4
+ ---
5
+
6
+ == Purpose
7
+
8
+ Tutorials provide step-by-step learning paths for mastering MessagePack Ruby.
9
+
10
+ == Tutorials
11
+
12
+ * link:../tutorials/getting-started[Getting started] - Introduction to basic serialization
13
+ * link:../tutorials/extension-types[Custom extension types] - Creating custom type mappings
14
+ * link:../tutorials/thread-safety[Thread-safe usage] - Using factories in multi-threaded applications
@@ -0,0 +1,157 @@
1
+ ---
2
+ title: Thread-safe usage
3
+ nav_order: 3
4
+ ---
5
+
6
+ == Purpose
7
+
8
+ This tutorial explains how to use MessagePack factories safely in multi-threaded
9
+ applications.
10
+
11
+ == References
12
+
13
+ * link:../pages/factory-pattern[Factory pattern] - Factory concepts
14
+ * link:../guides/performance[Performance guide] - Performance optimization
15
+
16
+ == Prerequisites
17
+
18
+ * Completed link:../tutorials/getting-started[Getting started] tutorial
19
+ * Basic understanding of Ruby threads
20
+
21
+ == Understanding thread safety
22
+
23
+ The `Messagepack::Factory` class provides two approaches for thread-safe usage:
24
+
25
+ * **Frozen factory** - Safe for concurrent reads
26
+ * **Factory pool** - Safe for concurrent reads and writes
27
+
28
+ == Using frozen factories
29
+
30
+ For concurrent serialization with custom types:
31
+
32
+ [source,ruby]
33
+ ----
34
+ # Create and configure factory
35
+ factory = Messagepack::Factory.new
36
+ factory.register_type(0x01, MyClass, packer: ..., unpacker: ...)
37
+
38
+ # Freeze to make thread-safe
39
+ factory.freeze
40
+
41
+ # Now safe to use from multiple threads
42
+ threads = 10.times.map do
43
+ Thread.new { factory.pack(my_object) }
44
+ end
45
+ results = threads.map(&:value)
46
+ ----
47
+
48
+ NOTE: Frozen factories are safe for concurrent packing but not for unpacking
49
+ from a shared unpacker. Use pools for full thread safety.
50
+
51
+ == Using factory pools
52
+
53
+ Pools provide the highest level of thread safety:
54
+
55
+ [source,ruby]
56
+ ----
57
+ # Create pool with 5 instances
58
+ pool = factory.pool(5)
59
+
60
+ # Use from multiple threads
61
+ threads = 20.times.map do |i|
62
+ Thread.new do
63
+ # Each thread gets its own packer/unpacker from the pool
64
+ data = { id: i, value: "thread-#{i}" }
65
+ binary = pool.pack(data)
66
+ pool.unpack(binary)
67
+ end
68
+ end
69
+
70
+ results = threads.map(&:value)
71
+ ----
72
+
73
+ === Pool size guidelines
74
+
75
+ Choose pool size based on your concurrency needs:
76
+
77
+ [source,ruby]
78
+ ----
79
+ # For thread pool of 10 workers
80
+ pool = factory.pool(10)
81
+
82
+ # For unlimited concurrency, use number of CPU cores
83
+ pool = factory.pool(Erl::Concurrency.available_processors)
84
+
85
+ # Default: 4 instances
86
+ pool = factory.pool(4)
87
+ ----
88
+
89
+ == Practical example: Web server
90
+
91
+ [source,ruby]
92
+ ----
93
+ require 'messagepack'
94
+
95
+ class Cache
96
+ def initialize
97
+ @factory = Messagepack::Factory.new
98
+ @factory.register_type(0x01, CachedObject, ...)
99
+ @pool = @factory.pool(10)
100
+ end
101
+
102
+ def put(key, value)
103
+ binary = @pool.pack(value)
104
+ redis.set("cache:#{key}", binary)
105
+ end
106
+
107
+ def get(key)
108
+ data = redis.get("cache:#{key}")
109
+ return nil unless data
110
+ @pool.unpack(data)
111
+ end
112
+ end
113
+
114
+ # Thread-safe for web server
115
+ cache = Cache.new
116
+
117
+ # In web requests (concurrent)
118
+ Thread.new do
119
+ cache.put("user:123", user_data)
120
+ end
121
+
122
+ Thread.new do
123
+ user = cache.get("user:123")
124
+ end
125
+ ----
126
+
127
+ == Performance considerations
128
+
129
+ [source,ruby]
130
+ ----
131
+ # Benchmark different approaches
132
+ require 'benchmark'
133
+
134
+ n = 10000
135
+
136
+ Benchmark.bm do |x|
137
+ x.report("pack:") do
138
+ factory = Messagepack::Factory.new
139
+ n.times { factory.pack(data) }
140
+ end
141
+
142
+ x.report("pool(2):") do
143
+ pool = factory.pool(2)
144
+ n.times { pool.pack(data) }
145
+ end
146
+
147
+ x.report("pool(10):") do
148
+ pool = factory.pool(10)
149
+ n.times { pool.pack(data) }
150
+ end
151
+ end
152
+ ----
153
+
154
+ == Next steps
155
+
156
+ * link:../guides/performance[Performance guide] - Optimize for performance
157
+ * link:../references/api[API reference] - Complete API documentation
data/docs/index.adoc ADDED
@@ -0,0 +1,77 @@
1
+ ---
2
+ title: MessagePack Ruby
3
+ nav_order: 1
4
+ ---
5
+
6
+ == Purpose
7
+
8
+ MessagePack Ruby is a pure Ruby implementation of the https://msgpack.org/[MessagePack] binary serialization format. MessagePack is an efficient binary serialization format that enables exchange of data among multiple languages like JSON, but is faster and smaller.
9
+
10
+ This implementation provides:
11
+
12
+ * Pure Ruby implementation (no C extension required)
13
+ * Full compatibility with the MessagePack specification
14
+ * Support for custom extension types
15
+ * Thread-safe factory pattern for packer/unpacker reuse
16
+ * Streaming unpacker for incremental parsing
17
+ * Comprehensive timestamp support with nanosecond precision
18
+
19
+ == Getting started
20
+
21
+ === Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ [source,ruby]
26
+ ----
27
+ gem 'messagepack'
28
+ ----
29
+
30
+ And then execute:
31
+
32
+ [source,shell]
33
+ ----
34
+ bundle install
35
+ ----
36
+
37
+ Or install it yourself as:
38
+
39
+ [source,shell]
40
+ ----
41
+ gem install messagepack
42
+ ----
43
+
44
+ === Quick start
45
+
46
+ [source,ruby]
47
+ ----
48
+ require 'messagepack'
49
+
50
+ # Serialize data
51
+ data = { hello: "world", count: 42 }
52
+ binary = Messagepack.pack(data)
53
+
54
+ # Deserialize data
55
+ result = Messagepack.unpack(binary)
56
+ # => {"hello"=>"world", "count"=>42}
57
+ ----
58
+
59
+ == Core concepts
60
+
61
+ * link:pages/serialization[Serialization] - Converting Ruby objects to binary format and back
62
+ * link:pages/factory-pattern[Factory pattern] - Thread-safe packer/unpacker management
63
+ * link:pages/extension-types[Extension types] - Custom type registration system
64
+ * link:pages/buffer[Buffer management] - Efficient chunked binary data storage
65
+ * link:pages/streaming[Streaming] - Incremental data parsing
66
+
67
+ == Learn more
68
+
69
+ * link:tutorials/getting-started[Getting started tutorial] - Step-by-step introduction
70
+ * link:guides/performance[Performance guide] - Optimization techniques and best practices
71
+ * link:references/api[API reference] - Complete API documentation
72
+
73
+ == External resources
74
+
75
+ * https://msgpack.org/[MessagePack specification] - Official format specification
76
+ * https://github.com/msgpack/msgpack/blob/master/spec.md[MessagePack spec] - Detailed format documentation
77
+ * https://github.com/lutaml/messagepack[GitHub repository] - Source code and issues
data/docs/lychee.toml ADDED
@@ -0,0 +1,42 @@
1
+ # Lychee link checker configuration
2
+
3
+ # Exclude specific URLs
4
+ exclude = [
5
+ # Exclude localhost links
6
+ "http://localhost",
7
+ "https://localhost",
8
+
9
+ # Exclude example domains that may not resolve
10
+ "http://example.com",
11
+ "https://example.com",
12
+
13
+ # Exclude links that require authentication
14
+ "https://github.com/lutaml/*",
15
+ ]
16
+
17
+ # Only check http and https links
18
+ schemes = ["http", "https"]
19
+
20
+ # Maximum number of concurrent requests
21
+ max_concurrency = 8
22
+
23
+ # Maximum number of redirects to follow
24
+ max_redirects = 10
25
+
26
+ # Request timeout in seconds
27
+ timeout = 20
28
+
29
+ # User agent for requests
30
+ user_agent = "lychee/0.14.0"
31
+
32
+ # Accept 200, 204, 301, 302, 304, 307, 308 status codes
33
+ accept = [200, 204, 301, 302, 304, 307, 308]
34
+
35
+ # Method to use for requests (GET or HEAD)
36
+ method = "get"
37
+
38
+ # Verbose output
39
+ verbose = false
40
+
41
+ # Do not show progress
42
+ no_progress = true
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'extensions/value'
4
+
5
+ module Messagepack
6
+ # Bigint extension type for arbitrary precision integers.
7
+ #
8
+ # This class handles integers that are too large for 64-bit representation.
9
+ # Uses the same format as msgpack-ruby: 32-bit big-endian chunks.
10
+ #
11
+ class Bigint
12
+ # Bigint extension type ID
13
+ TYPE = 0
14
+
15
+ # Number of bits per chunk
16
+ CHUNK_BITLENGTH = 32
17
+
18
+ # Ruby pack format: sign byte + 32-bit big-endian chunks
19
+ FORMAT = 'CL>*'
20
+
21
+ attr_reader :data
22
+
23
+ def initialize(data)
24
+ @data = data
25
+ end
26
+
27
+ # Serialize an integer to MessagePack bigint extension format.
28
+ #
29
+ # @param int [Integer] The integer to serialize
30
+ # @return [String] Binary payload
31
+ #
32
+ def self.to_msgpack_ext(int)
33
+ # Format: [sign(1 byte)][32-bit big-endian chunks...]
34
+ # sign: 0 for positive, 1 for negative
35
+ # chunks: absolute value split into 32-bit pieces, LSB first, each in big-endian byte order
36
+
37
+ if int == 0
38
+ return "\x00".b
39
+ end
40
+
41
+ members = []
42
+
43
+ # Sign byte
44
+ if int < 0
45
+ int = -int
46
+ members << 1
47
+ else
48
+ members << 0
49
+ end
50
+
51
+ # Split into 32-bit chunks (least significant chunk first)
52
+ base = (2 ** CHUNK_BITLENGTH) - 1
53
+ while int > 0
54
+ members << (int & base)
55
+ int >>= CHUNK_BITLENGTH
56
+ end
57
+
58
+ # Pack as sign byte + 32-bit big-endian chunks
59
+ members.pack(FORMAT)
60
+ end
61
+
62
+ # Deserialize from binary payload.
63
+ #
64
+ # @param data [String] Binary payload
65
+ # @return [Integer] The deserialized integer
66
+ #
67
+ def self.from_msgpack_ext(data)
68
+ return 0 if data.nil? || data.empty?
69
+
70
+ # Unpack as sign byte + 32-bit big-endian chunks
71
+ parts = data.unpack(FORMAT)
72
+
73
+ return 0 if parts.empty?
74
+
75
+ sign = parts.shift
76
+
77
+ return 0 if parts.empty?
78
+
79
+ # Reconstruct integer from chunks (LSB first)
80
+ sum = parts.pop.to_i
81
+ parts.reverse_each do |part|
82
+ sum = (sum << CHUNK_BITLENGTH) | part.to_i
83
+ end
84
+
85
+ sign == 0 ? sum : -sum
86
+ end
87
+
88
+ # Create a Bigint from an integer.
89
+ #
90
+ # @param int [Integer] The integer
91
+ # @return [Bigint] A new Bigint instance
92
+ #
93
+ def self.from_int(int)
94
+ new(to_msgpack_ext(int))
95
+ end
96
+
97
+ # Convert to integer.
98
+ #
99
+ # @return [Integer] The integer value
100
+ #
101
+ def to_int
102
+ self.class.from_msgpack_ext(@data)
103
+ end
104
+
105
+ # Equality comparison.
106
+ #
107
+ # @param other [Object] Another object
108
+ # @return [Boolean]
109
+ #
110
+ def ==(other)
111
+ return false unless other.is_a?(Bigint)
112
+ to_int == other.to_int
113
+ end
114
+
115
+ alias eql? ==
116
+
117
+ def hash
118
+ to_int.hash
119
+ end
120
+
121
+ # String representation.
122
+ #
123
+ # @return [String]
124
+ #
125
+ def to_s
126
+ "Bigint(#{@data.bytes.map { |b| '0x%02x' % b }.join(' ')})"
127
+ end
128
+
129
+ alias inspect to_s
130
+ end
131
+ end