Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,45 @@ jobs:
path: lib/cpp/test/*.xml
retention-days: 3

lib-ruby:
needs: compiler
runs-on: ubuntu-24.04
strategy:
matrix:
ruby-version: ["2.7", "3.0", "3.1", "3.2", "3.3", "3.4", "4.0.0-preview2"]
fail-fast: false
steps:
- uses: actions/checkout@v5

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true
working-directory: lib/rb

- name: Run bootstrap
run: ./bootstrap.sh

- name: Run configure
run: |
./configure $(echo $CONFIG_ARGS_FOR_LIBS | sed 's/without-ruby/with-ruby/')

- uses: actions/download-artifact@v6
with:
name: thrift-compiler
path: compiler/cpp

- name: Run thrift-compiler
run: |
chmod a+x compiler/cpp/thrift
compiler/cpp/thrift -version

- name: Run tests
run: |
cd lib/rb
bundle exec rake spec

cross-test:
needs:
- lib-java-kotlin
Expand Down
4 changes: 2 additions & 2 deletions LANGUAGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,8 @@ Thrift's core protocol is TBinary, supported by all languages except for JavaScr
<td align=left><a href="https://github.com/apache/thrift/blob/master/lib/rb/README.md">Ruby</a></td>
<!-- Since -----------------><td>0.2.0</td>
<!-- Build Systems ---------><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td>
<!-- Language Levels -------><td>2.3.1p112</td><td>2.5.1p57</td>
<!-- Field types -----------><td><img src="/doc/images/cred.png" alt=""/></td>
<!-- Language Levels -------><td>2.7.8</td><td>4.0.0</td>
<!-- Field types -----------><td><img src="/doc/images/cgrn.png" alt="Yes"/></td>
<!-- Low-Level Transports --><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td>
<!-- Transport Wrappers ----><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td>
<!-- Protocols -------------><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td>
Expand Down
5 changes: 5 additions & 0 deletions compiler/cpp/src/thrift/generate/t_rb_generator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,9 @@ t_rb_ofstream& t_rb_generator::render_const_value(t_rb_ofstream& out,
case t_base_type::TYPE_STRING:
out << "%q\"" << get_escaped_string(value) << '"';
break;
case t_base_type::TYPE_UUID:
out << "%q\"" << get_escaped_string(value) << '"';
break;
case t_base_type::TYPE_BOOL:
out << (value->get_integer() > 0 ? "true" : "false");
break;
Expand Down Expand Up @@ -1175,6 +1178,8 @@ string t_rb_generator::type_to_enum(t_type* type) {
return "::Thrift::Types::I64";
case t_base_type::TYPE_DOUBLE:
return "::Thrift::Types::DOUBLE";
case t_base_type::TYPE_UUID:
return "::Thrift::Types::UUID";
default:
throw "compiler error: unhandled type";
}
Expand Down
87 changes: 87 additions & 0 deletions lib/rb/ext/binary_protocol_accelerated.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <ruby.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <constants.h>
#include <struct.h>
#include <macros.h>
Expand Down Expand Up @@ -86,6 +87,19 @@ static void write_string_direct(VALUE trans, VALUE str) {
rb_funcall(trans, write_method_id, 1, str);
}

// Efficient hex character to integer conversion
static inline int hex_char_to_int(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1; // invalid hex character
}

// Efficient integer to hex character conversion
static inline char int_to_hex_char(int val) {
return val < 10 ? ('0' + val) : ('a' + val - 10);
}

//--------------------------------
// interface writing methods
//--------------------------------
Expand Down Expand Up @@ -228,6 +242,55 @@ VALUE rb_thrift_binary_proto_write_binary(VALUE self, VALUE buf) {
return Qnil;
}

VALUE rb_thrift_binary_proto_write_uuid(VALUE self, VALUE uuid) {
CHECK_NIL(uuid);

if (TYPE(uuid) != T_STRING) {
rb_raise(rb_eStandardError, "UUID must be a string");
}

VALUE trans = GET_TRANSPORT(self);
char bytes[16];
const char* str = RSTRING_PTR(uuid);
long len = RSTRING_LEN(uuid);

// Parse UUID string (format: "550e8400-e29b-41d4-a716-446655440000")
// Expected length: 36 characters (32 hex + 4 hyphens)
if (len != 36 || str[8] != '-' || str[13] != '-' || str[18] != '-' || str[23] != '-') {
VALUE exception_class = rb_const_get(thrift_module, rb_intern("ProtocolException"));
VALUE invalid_data = rb_const_get(exception_class, rb_intern("INVALID_DATA"));
VALUE args[2];
args[0] = invalid_data;
args[1] = rb_str_new2("Invalid UUID format");
rb_exc_raise(rb_class_new_instance(2, args, exception_class));
}

// Parse hex string to bytes using direct conversion, skipping hyphens
int byte_idx = 0;
for (int i = 0; i < len && byte_idx < 16; i++) {
if (str[i] == '-') continue;

// Convert two hex characters to one byte
int high = hex_char_to_int(str[i]);
int low = hex_char_to_int(str[i + 1]);

if (high < 0 || low < 0) {
VALUE exception_class = rb_const_get(thrift_module, rb_intern("ProtocolException"));
VALUE invalid_data = rb_const_get(exception_class, rb_intern("INVALID_DATA"));
VALUE args[2];
args[0] = invalid_data;
args[1] = rb_str_new2("Invalid hex character in UUID");
rb_exc_raise(rb_class_new_instance(2, args, exception_class));
}

bytes[byte_idx++] = (unsigned char)((high << 4) | low);
i++; // skip next char since we processed two
}

WRITE(trans, bytes, 16);
return Qnil;
}

//---------------------------------------
// interface reading methods
//---------------------------------------
Expand Down Expand Up @@ -400,6 +463,28 @@ VALUE rb_thrift_binary_proto_read_binary(VALUE self) {
return READ(self, size);
}

VALUE rb_thrift_binary_proto_read_uuid(VALUE self) {
unsigned char bytes[16];
VALUE data = READ(self, 16);
memcpy(bytes, RSTRING_PTR(data), 16);

// Format as UUID string: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
char uuid_str[37];
char* p = uuid_str;

for (int i = 0; i < 16; i++) {
*p++ = int_to_hex_char((bytes[i] >> 4) & 0x0F);
*p++ = int_to_hex_char(bytes[i] & 0x0F);
if (i == 3 || i == 5 || i == 7 || i == 9) {
*p++ = '-';
}
}

*p = '\0';

return rb_str_new(uuid_str, 36);
}

void Init_binary_protocol_accelerated() {
VALUE thrift_binary_protocol_class = rb_const_get(thrift_module, rb_intern("BinaryProtocol"));

Expand All @@ -425,6 +510,7 @@ void Init_binary_protocol_accelerated() {
rb_define_method(bpa_class, "write_double", rb_thrift_binary_proto_write_double, 1);
rb_define_method(bpa_class, "write_string", rb_thrift_binary_proto_write_string, 1);
rb_define_method(bpa_class, "write_binary", rb_thrift_binary_proto_write_binary, 1);
rb_define_method(bpa_class, "write_uuid", rb_thrift_binary_proto_write_uuid, 1);
// unused methods
rb_define_method(bpa_class, "write_message_end", rb_thrift_binary_proto_write_message_end, 0);
rb_define_method(bpa_class, "write_struct_begin", rb_thrift_binary_proto_write_struct_begin, 1);
Expand All @@ -447,6 +533,7 @@ void Init_binary_protocol_accelerated() {
rb_define_method(bpa_class, "read_double", rb_thrift_binary_proto_read_double, 0);
rb_define_method(bpa_class, "read_string", rb_thrift_binary_proto_read_string, 0);
rb_define_method(bpa_class, "read_binary", rb_thrift_binary_proto_read_binary, 0);
rb_define_method(bpa_class, "read_uuid", rb_thrift_binary_proto_read_uuid, 0);
// unused methods
rb_define_method(bpa_class, "read_message_end", rb_thrift_binary_proto_read_message_end, 0);
rb_define_method(bpa_class, "read_struct_begin", rb_thrift_binary_proto_read_struct_begin, 0);
Expand Down
5 changes: 5 additions & 0 deletions lib/rb/ext/compact_protocol.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ static int CTYPE_LIST = 0x09;
static int CTYPE_SET = 0x0A;
static int CTYPE_MAP = 0x0B;
static int CTYPE_STRUCT = 0x0C;
static int CTYPE_UUID = 0x0D;

VALUE rb_thrift_compact_proto_write_i16(VALUE self, VALUE i16);

Expand Down Expand Up @@ -86,6 +87,8 @@ static int get_compact_type(VALUE type_value) {
return CTYPE_MAP;
} else if (type == TTYPE_STRUCT) {
return CTYPE_STRUCT;
} else if (type == TTYPE_UUID) {
return CTYPE_UUID;
} else {
char str[50];
sprintf(str, "don't know what type: %d", type);
Expand Down Expand Up @@ -357,6 +360,8 @@ static int8_t get_ttype(int8_t ctype) {
return TTYPE_MAP;
} else if (ctype == CTYPE_STRUCT) {
return TTYPE_STRUCT;
} else if (ctype == CTYPE_UUID) {
return TTYPE_UUID;
} else {
char str[50];
sprintf(str, "don't know what type: %d", ctype);
Expand Down
3 changes: 3 additions & 0 deletions lib/rb/ext/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ extern int TTYPE_MAP;
extern int TTYPE_SET;
extern int TTYPE_LIST;
extern int TTYPE_STRUCT;
extern int TTYPE_UUID;

extern ID validate_method_id;
extern ID write_struct_begin_method_id;
Expand All @@ -49,6 +50,7 @@ extern ID write_list_begin_method_id;
extern ID write_list_end_method_id;
extern ID write_set_begin_method_id;
extern ID write_set_end_method_id;
extern ID write_uuid_method_id;
extern ID read_bool_method_id;
extern ID read_byte_method_id;
extern ID read_i16_method_id;
Expand All @@ -63,6 +65,7 @@ extern ID read_list_begin_method_id;
extern ID read_list_end_method_id;
extern ID read_set_begin_method_id;
extern ID read_set_end_method_id;
extern ID read_uuid_method_id;
extern ID read_struct_begin_method_id;
extern ID read_struct_end_method_id;
extern ID read_field_begin_method_id;
Expand Down
13 changes: 13 additions & 0 deletions lib/rb/ext/struct.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ VALUE default_write_string(VALUE protocol, VALUE value) {
return Qnil;
}

VALUE default_write_uuid(VALUE protocol, VALUE value) {
rb_funcall(protocol, write_uuid_method_id, 1, value);
return Qnil;
}

VALUE default_write_binary(VALUE protocol, VALUE value) {
rb_funcall(protocol, write_binary_method_id, 1, value);
return Qnil;
Expand Down Expand Up @@ -195,6 +200,10 @@ VALUE default_read_string(VALUE protocol) {
return rb_funcall(protocol, read_string_method_id, 0);
}

VALUE default_read_uuid(VALUE protocol) {
return rb_funcall(protocol, read_uuid_method_id, 0);
}

VALUE default_read_binary(VALUE protocol) {
return rb_funcall(protocol, read_binary_method_id, 0);
}
Expand Down Expand Up @@ -342,6 +351,8 @@ static void write_anything(int ttype, VALUE value, VALUE protocol, VALUE field_i
} else {
default_write_binary(protocol, value);
}
} else if (ttype == TTYPE_UUID) {
default_write_uuid(protocol, value);
} else if (IS_CONTAINER(ttype)) {
write_container(ttype, field_info, value, protocol);
} else if (ttype == TTYPE_STRUCT) {
Expand Down Expand Up @@ -452,6 +463,8 @@ static VALUE read_anything(VALUE protocol, int ttype, VALUE field_info) {
}
} else if (ttype == TTYPE_DOUBLE) {
result = default_read_double(protocol);
} else if (ttype == TTYPE_UUID) {
result = default_read_uuid(protocol);
} else if (ttype == TTYPE_STRUCT) {
VALUE klass = rb_hash_aref(field_info, class_sym);
result = rb_class_new_instance(0, NULL, klass);
Expand Down
10 changes: 8 additions & 2 deletions lib/rb/ext/thrift_native.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ int TTYPE_MAP;
int TTYPE_SET;
int TTYPE_LIST;
int TTYPE_STRUCT;
int TTYPE_UUID;

// method ids
ID validate_method_id;
Expand All @@ -57,6 +58,7 @@ ID write_i32_method_id;
ID write_i64_method_id;
ID write_double_method_id;
ID write_string_method_id;
ID write_uuid_method_id;
ID write_binary_method_id;
ID write_map_begin_method_id;
ID write_map_end_method_id;
Expand All @@ -70,6 +72,7 @@ ID read_i16_method_id;
ID read_i32_method_id;
ID read_i64_method_id;
ID read_string_method_id;
ID read_uuid_method_id;
ID read_binary_method_id;
ID read_double_method_id;
ID read_map_begin_method_id;
Expand Down Expand Up @@ -138,6 +141,7 @@ void Init_thrift_native() {
TTYPE_SET = FIX2INT(rb_const_get(thrift_types_module, rb_intern("SET")));
TTYPE_LIST = FIX2INT(rb_const_get(thrift_types_module, rb_intern("LIST")));
TTYPE_STRUCT = FIX2INT(rb_const_get(thrift_types_module, rb_intern("STRUCT")));
TTYPE_UUID = FIX2INT(rb_const_get(thrift_types_module, rb_intern("UUID")));

// method ids
validate_method_id = rb_intern("validate");
Expand All @@ -152,6 +156,7 @@ void Init_thrift_native() {
write_i64_method_id = rb_intern("write_i64");
write_double_method_id = rb_intern("write_double");
write_string_method_id = rb_intern("write_string");
write_uuid_method_id = rb_intern("write_uuid");
write_binary_method_id = rb_intern("write_binary");
write_map_begin_method_id = rb_intern("write_map_begin");
write_map_end_method_id = rb_intern("write_map_end");
Expand All @@ -165,10 +170,11 @@ void Init_thrift_native() {
read_i32_method_id = rb_intern("read_i32");
read_i64_method_id = rb_intern("read_i64");
read_string_method_id = rb_intern("read_string");
read_uuid_method_id = rb_intern("read_uuid");
read_binary_method_id = rb_intern("read_binary");
read_double_method_id = rb_intern("read_double");
read_map_begin_method_id = rb_intern("read_map_begin");
read_map_end_method_id = rb_intern("read_map_end");
read_map_end_method_id = rb_intern("read_map_end");
read_list_begin_method_id = rb_intern("read_list_begin");
read_list_end_method_id = rb_intern("read_list_end");
read_set_begin_method_id = rb_intern("read_set_begin");
Expand All @@ -192,7 +198,7 @@ void Init_thrift_native() {
fields_const_id = rb_intern("FIELDS");
transport_ivar_id = rb_intern("@trans");
strict_read_ivar_id = rb_intern("@strict_read");
strict_write_ivar_id = rb_intern("@strict_write");
strict_write_ivar_id = rb_intern("@strict_write");

// cached symbols
type_sym = ID2SYM(rb_intern("type"));
Expand Down
1 change: 0 additions & 1 deletion lib/rb/lib/thrift.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
$:.unshift File.dirname(__FILE__)

require 'thrift/bytes'
require 'thrift/core_ext'
require 'thrift/exceptions'
require 'thrift/types'
require 'thrift/processor'
Expand Down
23 changes: 0 additions & 23 deletions lib/rb/lib/thrift/core_ext.rb

This file was deleted.

Loading