Added UUID support in Ruby library
diff --git a/lib/rb/Rakefile b/lib/rb/Rakefile
index c6b650e..53d6096 100644
--- a/lib/rb/Rakefile
+++ b/lib/rb/Rakefile
@@ -62,10 +62,10 @@
dir = File.dirname(__FILE__) + '/benchmark'
sh THRIFT, '--gen', 'rb', '-o', dir, "#{dir}/Benchmark.thrift"
end
-
+
task :'debug_proto' do
sh "mkdir", "-p", "test/debug_proto"
- sh THRIFT, '--gen', 'rb', "-o", "test/debug_proto", "../../test/v0.16/DebugProtoTest.thrift"
+ sh THRIFT, '--gen', 'rb', "-o", "test/debug_proto", "../../test/DebugProtoTest.thrift"
end
end
diff --git a/lib/rb/ext/binary_protocol_accelerated.c b/lib/rb/ext/binary_protocol_accelerated.c
index 3240fdf..791dfa7 100644
--- a/lib/rb/ext/binary_protocol_accelerated.c
+++ b/lib/rb/ext/binary_protocol_accelerated.c
@@ -20,10 +20,12 @@
#include <ruby.h>
#include <stdbool.h>
#include <stdint.h>
+#include <string.h>
#include <constants.h>
#include <struct.h>
#include <macros.h>
#include <bytes.h>
+#include <protocol.h>
VALUE rb_thrift_binary_proto_native_qmark(VALUE self) {
return Qtrue;
@@ -34,7 +36,6 @@
static int VERSION_1;
static int VERSION_MASK;
static int TYPE_MASK;
-static int BAD_VERSION;
static ID rbuf_ivar_id;
static void write_byte_direct(VALUE trans, int8_t b) {
@@ -228,6 +229,46 @@
return Qnil;
}
+VALUE rb_thrift_binary_proto_write_uuid(VALUE self, VALUE uuid) {
+ if (NIL_P(uuid) || TYPE(uuid) != T_STRING) {
+ rb_exc_raise(get_protocol_exception(INT2FIX(PROTOERR_INVALID_DATA), rb_str_new2("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] != '-') {
+ rb_exc_raise(get_protocol_exception(INT2FIX(PROTOERR_INVALID_DATA), rb_str_new2("Invalid UUID format")));
+ }
+
+ // 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;
+ if (i + 1 >= len || str[i + 1] == '-') break;
+
+ // 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) break;
+
+ bytes[byte_idx++] = (unsigned char)((high << 4) | low);
+ i++; // skip next char since we processed two
+ }
+
+ if (byte_idx != 16) {
+ rb_exc_raise(get_protocol_exception(INT2FIX(PROTOERR_INVALID_DATA), rb_str_new2("Invalid UUID format")));
+ }
+
+ WRITE(trans, bytes, 16);
+ return Qnil;
+}
+
//---------------------------------------
// interface reading methods
//---------------------------------------
@@ -272,13 +313,6 @@
return (hi << 32) | lo;
}
-static VALUE get_protocol_exception(VALUE code, VALUE message) {
- VALUE args[2];
- args[0] = code;
- args[1] = message;
- return rb_class_new_instance(2, (VALUE*)&args, protocol_exception_class);
-}
-
VALUE rb_thrift_binary_proto_read_message_end(VALUE self) {
return Qnil;
}
@@ -316,14 +350,14 @@
if (version < 0) {
if ((version & VERSION_MASK) != VERSION_1) {
- rb_exc_raise(get_protocol_exception(INT2FIX(BAD_VERSION), rb_str_new2("Missing version identifier")));
+ rb_exc_raise(get_protocol_exception(INT2FIX(PROTOERR_BAD_VERSION), rb_str_new2("Missing version identifier")));
}
type = version & TYPE_MASK;
name = rb_thrift_binary_proto_read_string(self);
seqid = rb_thrift_binary_proto_read_i32(self);
} else {
if (strict_read == Qtrue) {
- rb_exc_raise(get_protocol_exception(INT2FIX(BAD_VERSION), rb_str_new2("No version identifier, old protocol client?")));
+ rb_exc_raise(get_protocol_exception(INT2FIX(PROTOERR_BAD_VERSION), rb_str_new2("No version identifier, old protocol client?")));
}
name = READ(self, version);
type = read_byte_direct(self);
@@ -400,6 +434,27 @@
return READ(self, size);
}
+VALUE rb_thrift_binary_proto_read_uuid(VALUE self) {
+ VALUE data = READ(self, 16);
+ const unsigned char* bytes = (const unsigned char*)RSTRING_PTR(data);
+
+ // 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(void) {
VALUE thrift_binary_protocol_class = rb_const_get(thrift_module, rb_intern("BinaryProtocol"));
@@ -425,6 +480,7 @@
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);
@@ -447,6 +503,7 @@
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);
diff --git a/lib/rb/ext/compact_protocol.c b/lib/rb/ext/compact_protocol.c
index 62c17e1..8c864e8 100644
--- a/lib/rb/ext/compact_protocol.c
+++ b/lib/rb/ext/compact_protocol.c
@@ -20,10 +20,12 @@
#include <ruby.h>
#include <stdbool.h>
#include <stdint.h>
+#include <string.h>
#include <constants.h>
#include <struct.h>
#include <macros.h>
#include <bytes.h>
+#include <protocol.h>
#define LAST_ID(obj) FIX2INT(rb_ary_pop(rb_ivar_get(obj, last_field_id)))
#define SET_LAST_ID(obj, val) rb_ary_push(rb_ivar_get(obj, last_field_id), val)
@@ -58,6 +60,7 @@
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);
@@ -86,6 +89,8 @@
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);
@@ -169,6 +174,7 @@
VALUE rb_thrift_compact_proto_write_i32(VALUE self, VALUE i32);
VALUE rb_thrift_compact_proto_write_string(VALUE self, VALUE str);
VALUE rb_thrift_compact_proto_write_binary(VALUE self, VALUE buf);
+VALUE rb_thrift_compact_proto_write_uuid(VALUE self, VALUE uuid);
VALUE rb_thrift_compact_proto_write_message_end(VALUE self) {
return Qnil;
@@ -320,6 +326,46 @@
return Qnil;
}
+VALUE rb_thrift_compact_proto_write_uuid(VALUE self, VALUE uuid) {
+ if (NIL_P(uuid) || TYPE(uuid) != T_STRING) {
+ rb_exc_raise(get_protocol_exception(INT2FIX(PROTOERR_INVALID_DATA), rb_str_new2("UUID must be a string")));
+ }
+
+ VALUE transport = 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] != '-') {
+ rb_exc_raise(get_protocol_exception(INT2FIX(PROTOERR_INVALID_DATA), rb_str_new2("Invalid UUID format")));
+ }
+
+ // 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;
+ if (i + 1 >= len || str[i + 1] == '-') break;
+
+ // 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) break;
+
+ bytes[byte_idx++] = (unsigned char)((high << 4) | low);
+ i++; // skip next char since we processed two
+ }
+
+ if (byte_idx != 16) {
+ rb_exc_raise(get_protocol_exception(INT2FIX(PROTOERR_INVALID_DATA), rb_str_new2("Invalid UUID format")));
+ }
+
+ WRITE(transport, bytes, 16);
+ return Qnil;
+}
+
//---------------------------------------
// interface reading methods
//---------------------------------------
@@ -331,6 +377,7 @@
VALUE rb_thrift_compact_proto_read_byte(VALUE self);
VALUE rb_thrift_compact_proto_read_i32(VALUE self);
VALUE rb_thrift_compact_proto_read_i16(VALUE self);
+VALUE rb_thrift_compact_proto_read_uuid(VALUE self);
static int8_t get_ttype(int8_t ctype) {
if (ctype == TTYPE_STOP) {
@@ -357,6 +404,8 @@
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);
@@ -396,13 +445,6 @@
return zig_zag_to_int((int32_t)read_varint64(self));
}
-static VALUE get_protocol_exception(VALUE code, VALUE message) {
- VALUE args[2];
- args[0] = code;
- args[1] = message;
- return rb_class_new_instance(2, (VALUE*)&args, protocol_exception_class);
-}
-
VALUE rb_thrift_compact_proto_read_message_end(VALUE self) {
return Qnil;
}
@@ -565,6 +607,27 @@
return READ(self, size);
}
+VALUE rb_thrift_compact_proto_read_uuid(VALUE self) {
+ VALUE data = READ(self, 16);
+ const unsigned char* bytes = (const unsigned char*)RSTRING_PTR(data);
+
+ // 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);
+}
+
static void Init_constants(void) {
thrift_compact_protocol_class = rb_const_get(thrift_module, rb_intern("CompactProtocol"));
rb_global_variable(&thrift_compact_protocol_class);
@@ -599,6 +662,7 @@
rb_define_method(thrift_compact_protocol_class, "write_double", rb_thrift_compact_proto_write_double, 1);
rb_define_method(thrift_compact_protocol_class, "write_string", rb_thrift_compact_proto_write_string, 1);
rb_define_method(thrift_compact_protocol_class, "write_binary", rb_thrift_compact_proto_write_binary, 1);
+ rb_define_method(thrift_compact_protocol_class, "write_uuid", rb_thrift_compact_proto_write_uuid, 1);
rb_define_method(thrift_compact_protocol_class, "write_message_end", rb_thrift_compact_proto_write_message_end, 0);
rb_define_method(thrift_compact_protocol_class, "write_struct_begin", rb_thrift_compact_proto_write_struct_begin, 1);
@@ -622,6 +686,7 @@
rb_define_method(thrift_compact_protocol_class, "read_double", rb_thrift_compact_proto_read_double, 0);
rb_define_method(thrift_compact_protocol_class, "read_string", rb_thrift_compact_proto_read_string, 0);
rb_define_method(thrift_compact_protocol_class, "read_binary", rb_thrift_compact_proto_read_binary, 0);
+ rb_define_method(thrift_compact_protocol_class, "read_uuid", rb_thrift_compact_proto_read_uuid, 0);
rb_define_method(thrift_compact_protocol_class, "read_message_end", rb_thrift_compact_proto_read_message_end, 0);
rb_define_method(thrift_compact_protocol_class, "read_struct_begin", rb_thrift_compact_proto_read_struct_begin, 0);
diff --git a/lib/rb/ext/constants.h b/lib/rb/ext/constants.h
index e7aec44..01a0d8d 100644
--- a/lib/rb/ext/constants.h
+++ b/lib/rb/ext/constants.h
@@ -29,6 +29,7 @@
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;
@@ -49,6 +50,7 @@
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;
@@ -63,6 +65,7 @@
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;
@@ -97,3 +100,12 @@
extern VALUE thrift_bytes_module;
extern VALUE class_thrift_protocol;
extern VALUE protocol_exception_class;
+
+// protocol errors
+extern int PROTOERR_UNKNOWN;
+extern int PROTOERR_INVALID_DATA;
+extern int PROTOERR_NEGATIVE_SIZE;
+extern int PROTOERR_SIZE_LIMIT;
+extern int PROTOERR_BAD_VERSION;
+extern int PROTOERR_NOT_IMPLEMENTED;
+extern int PROTOERR_DEPTH_LIMIT;
diff --git a/lib/rb/ext/protocol.c b/lib/rb/ext/protocol.c
index e69de29..d0fc721 100644
--- a/lib/rb/ext/protocol.c
+++ b/lib/rb/ext/protocol.c
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <ruby.h>
+#include <constants.h>
+#include <protocol.h>
+
+VALUE get_protocol_exception(VALUE code, VALUE message) {
+ VALUE args[2];
+ args[0] = code;
+ args[1] = message;
+ return rb_class_new_instance(2, (VALUE*)&args, protocol_exception_class);
+}
diff --git a/lib/rb/ext/protocol.h b/lib/rb/ext/protocol.h
index e69de29..174a346 100644
--- a/lib/rb/ext/protocol.h
+++ b/lib/rb/ext/protocol.h
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <ruby.h>
+
+VALUE get_protocol_exception(VALUE code, VALUE message);
+
+// 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);
+}
diff --git a/lib/rb/ext/struct.c b/lib/rb/ext/struct.c
index ad33a81..c36c161 100644
--- a/lib/rb/ext/struct.c
+++ b/lib/rb/ext/struct.c
@@ -75,6 +75,11 @@
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;
@@ -195,6 +200,10 @@
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);
}
@@ -342,6 +351,8 @@
} 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) {
@@ -452,6 +463,8 @@
}
} 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);
diff --git a/lib/rb/ext/thrift_native.c b/lib/rb/ext/thrift_native.c
index 7c99f5e..cd4faa9 100644
--- a/lib/rb/ext/thrift_native.c
+++ b/lib/rb/ext/thrift_native.c
@@ -43,6 +43,7 @@
int TTYPE_SET;
int TTYPE_LIST;
int TTYPE_STRUCT;
+int TTYPE_UUID;
// method ids
ID validate_method_id;
@@ -57,6 +58,7 @@
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;
@@ -70,6 +72,7 @@
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;
@@ -109,6 +112,15 @@
VALUE binary_sym;
VALUE protocol_exception_class;
+// protocol errors
+int PROTOERR_UNKNOWN;
+int PROTOERR_INVALID_DATA;
+int PROTOERR_NEGATIVE_SIZE;
+int PROTOERR_SIZE_LIMIT;
+int PROTOERR_BAD_VERSION;
+int PROTOERR_NOT_IMPLEMENTED;
+int PROTOERR_DEPTH_LIMIT;
+
RUBY_FUNC_EXPORTED void Init_thrift_native(void) {
// cached classes
thrift_module = rb_const_get(rb_cObject, rb_intern("Thrift"));
@@ -138,6 +150,7 @@
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");
@@ -152,6 +165,7 @@
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");
@@ -165,6 +179,7 @@
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");
@@ -203,6 +218,15 @@
class_sym = ID2SYM(rb_intern("class"));
binary_sym = ID2SYM(rb_intern("binary"));
+ // protocol errors
+ PROTOERR_UNKNOWN = FIX2INT(rb_const_get(protocol_exception_class, rb_intern("UNKNOWN")));
+ PROTOERR_INVALID_DATA = FIX2INT(rb_const_get(protocol_exception_class, rb_intern("INVALID_DATA")));
+ PROTOERR_NEGATIVE_SIZE = FIX2INT(rb_const_get(protocol_exception_class, rb_intern("NEGATIVE_SIZE")));
+ PROTOERR_SIZE_LIMIT = FIX2INT(rb_const_get(protocol_exception_class, rb_intern("SIZE_LIMIT")));
+ PROTOERR_BAD_VERSION = FIX2INT(rb_const_get(protocol_exception_class, rb_intern("BAD_VERSION")));
+ PROTOERR_NOT_IMPLEMENTED = FIX2INT(rb_const_get(protocol_exception_class, rb_intern("NOT_IMPLEMENTED")));
+ PROTOERR_DEPTH_LIMIT = FIX2INT(rb_const_get(protocol_exception_class, rb_intern("DEPTH_LIMIT")));
+
rb_global_variable(&type_sym);
rb_global_variable(&name_sym);
rb_global_variable(&key_sym);
diff --git a/lib/rb/lib/thrift.rb b/lib/rb/lib/thrift.rb
index 5d83695..8b9a8a5 100644
--- a/lib/rb/lib/thrift.rb
+++ b/lib/rb/lib/thrift.rb
@@ -31,6 +31,7 @@
require 'thrift/struct'
require 'thrift/union'
require 'thrift/struct_union'
+require 'thrift/uuid'
# serializer
require 'thrift/serializer/serializer'
diff --git a/lib/rb/lib/thrift/protocol/base_protocol.rb b/lib/rb/lib/thrift/protocol/base_protocol.rb
index e9ea7c2..dfce44a 100644
--- a/lib/rb/lib/thrift/protocol/base_protocol.rb
+++ b/lib/rb/lib/thrift/protocol/base_protocol.rb
@@ -137,6 +137,15 @@
raise NotImplementedError
end
+ # Writes a UUID as 16 bytes.
+ #
+ # uuid - The UUID string to write (e.g. "550e8400-e29b-41d4-a716-446655440000").
+ #
+ # Returns nothing.
+ def write_uuid(uuid)
+ raise NotImplementedError
+ end
+
def read_message_begin
raise NotImplementedError
end
@@ -212,6 +221,13 @@
raise NotImplementedError
end
+ # Reads a UUID as 16 bytes and returns it as a string.
+ #
+ # Returns a String (e.g. "550e8400-e29b-41d4-a716-446655440000").
+ def read_uuid
+ raise NotImplementedError
+ end
+
# Writes a field based on the field information, field ID and value.
#
# field_info - A Hash containing the definition of the field:
@@ -276,6 +292,8 @@
else
write_string(value)
end
+ when Types::UUID
+ write_uuid(value)
when Types::STRUCT
value.write(self)
else
@@ -316,6 +334,8 @@
else
read_string
end
+ when Types::UUID
+ read_uuid
else
raise NotImplementedError
end
@@ -337,6 +357,8 @@
read_double
when Types::STRING
read_string
+ when Types::UUID
+ read_uuid
when Types::STRUCT
read_struct_begin
while true
diff --git a/lib/rb/lib/thrift/protocol/binary_protocol.rb b/lib/rb/lib/thrift/protocol/binary_protocol.rb
index ce5d0d7..39b72ff 100644
--- a/lib/rb/lib/thrift/protocol/binary_protocol.rb
+++ b/lib/rb/lib/thrift/protocol/binary_protocol.rb
@@ -123,6 +123,11 @@
trans.write(buf)
end
+ def write_uuid(uuid)
+ UUID.validate_uuid!(uuid)
+ trans.write(UUID.uuid_bytes(uuid))
+ end
+
def read_message_begin
version = read_i32
if version < 0
@@ -233,7 +238,11 @@
size = read_i32
trans.read_all(size)
end
-
+
+ def read_uuid
+ UUID.uuid_from_bytes(trans.read_all(16))
+ end
+
def to_s
"binary(#{super.to_s})"
end
diff --git a/lib/rb/lib/thrift/protocol/compact_protocol.rb b/lib/rb/lib/thrift/protocol/compact_protocol.rb
index 1ab9d3d..bc342e1 100644
--- a/lib/rb/lib/thrift/protocol/compact_protocol.rb
+++ b/lib/rb/lib/thrift/protocol/compact_protocol.rb
@@ -45,7 +45,8 @@
SET = 0x0A
MAP = 0x0B
STRUCT = 0x0C
-
+ UUID = 0x0D
+
def self.is_bool_type?(b)
(b & 0x0f) == BOOLEAN_TRUE || (b & 0x0f) == BOOLEAN_FALSE
end
@@ -63,7 +64,8 @@
LIST => Types::LIST,
SET => Types::SET,
MAP => Types::MAP,
- STRUCT => Types::STRUCT
+ STRUCT => Types::STRUCT,
+ UUID => Types::UUID
}
TTYPE_TO_COMPACT = {
@@ -78,7 +80,8 @@
Types::LIST => LIST,
Types::SET => SET,
Types::MAP => MAP,
- Types::STRUCT => STRUCT
+ Types::STRUCT => STRUCT,
+ Types::UUID => UUID
}
def self.get_ttype(compact_type)
@@ -220,18 +223,23 @@
@trans.write(buf)
end
+ def write_uuid(uuid)
+ UUID.validate_uuid!(uuid)
+ trans.write(UUID.uuid_bytes(uuid))
+ end
+
def read_message_begin
protocol_id = read_byte()
if protocol_id != PROTOCOL_ID
raise ProtocolException.new("Expected protocol id #{PROTOCOL_ID} but got #{protocol_id}")
end
-
+
version_and_type = read_byte()
version = version_and_type & VERSION_MASK
if (version != VERSION)
raise ProtocolException.new("Expected version #{VERSION} but got #{version}");
end
-
+
type = (version_and_type >> TYPE_SHIFT_AMOUNT) & TYPE_BITS
seqid = read_varint32()
messageName = read_string()
@@ -345,17 +353,21 @@
size = read_varint32()
trans.read_all(size)
end
-
+
+ def read_uuid
+ UUID.uuid_from_bytes(trans.read_all(16))
+ end
+
def to_s
"compact(#{super.to_s})"
end
private
-
- #
- # Abstract method for writing the start of lists and sets. List and sets on
+
+ #
+ # Abstract method for writing the start of lists and sets. List and sets on
# the wire differ only by the type indicator.
- #
+ #
def write_collection_begin(elem_type, size)
if size <= 14
write_byte(size << 4 | CompactTypes.get_compact_type(elem_type))
diff --git a/lib/rb/lib/thrift/protocol/json_protocol.rb b/lib/rb/lib/thrift/protocol/json_protocol.rb
index d03357e..09e3166 100644
--- a/lib/rb/lib/thrift/protocol/json_protocol.rb
+++ b/lib/rb/lib/thrift/protocol/json_protocol.rb
@@ -179,6 +179,8 @@
"set"
when Types::LIST
"lst"
+ when Types::UUID
+ "uid"
else
raise NotImplementedError
end
@@ -207,6 +209,8 @@
result = Types::SET
elsif (name == "lst")
result = Types::LIST
+ elsif (name == "uid")
+ result = Types::UUID
else
result = Types::STOP
end
@@ -475,6 +479,11 @@
write_json_base64(str)
end
+ def write_uuid(uuid)
+ UUID.validate_uuid!(uuid)
+ write_json_string(uuid.downcase)
+ end
+
##
# Reading functions
##
@@ -767,6 +776,13 @@
read_json_base64
end
+ def read_uuid
+ uuid = read_json_string
+ raise EOFError.new if uuid.length < 36
+ UUID.validate_uuid!(uuid)
+ uuid.tap(&:downcase!)
+ end
+
def to_s
"json(#{super.to_s})"
end
diff --git a/lib/rb/lib/thrift/protocol/protocol_decorator.rb b/lib/rb/lib/thrift/protocol/protocol_decorator.rb
index b1e3c15..73571f7 100644
--- a/lib/rb/lib/thrift/protocol/protocol_decorator.rb
+++ b/lib/rb/lib/thrift/protocol/protocol_decorator.rb
@@ -111,6 +111,10 @@
@protocol.write_binary(buf)
end
+ def write_uuid(uuid)
+ @protocol.write_uuid(uuid)
+ end
+
def read_message_begin
@protocol.read_message_begin
end
@@ -190,5 +194,9 @@
def read_binary
@protocol.read_binary
end
+
+ def read_uuid
+ @protocol.read_uuid
+ end
end
-end
\ No newline at end of file
+end
diff --git a/lib/rb/lib/thrift/types.rb b/lib/rb/lib/thrift/types.rb
index cac5269..0f6ab42 100644
--- a/lib/rb/lib/thrift/types.rb
+++ b/lib/rb/lib/thrift/types.rb
@@ -34,6 +34,7 @@
MAP = 13
SET = 14
LIST = 15
+ UUID = 16
end
class << self
@@ -56,6 +57,8 @@
Float
when Types::STRING
String
+ when Types::UUID
+ String
when Types::STRUCT
[Struct, Union]
when Types::MAP
diff --git a/lib/rb/lib/thrift/uuid.rb b/lib/rb/lib/thrift/uuid.rb
new file mode 100644
index 0000000..f704cfc
--- /dev/null
+++ b/lib/rb/lib/thrift/uuid.rb
@@ -0,0 +1,49 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require 'thrift/protocol/base_protocol'
+
+module Thrift
+ module UUID
+ UUID_REGEX = /\A[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\z/.freeze
+
+ def self.validate_uuid!(uuid)
+ unless uuid.is_a?(String)
+ raise ProtocolException.new(ProtocolException::INVALID_DATA, 'UUID must be a string')
+ end
+
+ unless uuid =~ UUID_REGEX
+ raise ProtocolException.new(ProtocolException::INVALID_DATA, 'Invalid UUID format')
+ end
+ end
+
+ def self.uuid_bytes(uuid)
+ [uuid.delete('-')].pack('H*')
+ end
+
+ def self.uuid_from_bytes(bytes)
+ unless bytes.bytesize == 16
+ raise ProtocolException.new(ProtocolException::INVALID_DATA, 'Invalid UUID data length')
+ end
+
+ hex = bytes.unpack('H*').first
+ "#{hex[0, 8]}-#{hex[8, 4]}-#{hex[12, 4]}-#{hex[16, 4]}-#{hex[20, 12]}"
+ end
+ end
+end
diff --git a/lib/rb/spec/ThriftSpec.thrift b/lib/rb/spec/ThriftSpec.thrift
index b42481b..3c2f3e0 100644
--- a/lib/rb/spec/ThriftSpec.thrift
+++ b/lib/rb/spec/ThriftSpec.thrift
@@ -60,6 +60,7 @@
3: i32 other_i32_field;
4: SomeEnum enum_field;
5: binary binary_field;
+ 6: uuid uuid_field;
}
struct Foo {
@@ -71,6 +72,7 @@
6: set<i16> shorts = [5, 17, 239],
7: optional string opt_string
8: bool my_bool
+ 9: optional uuid opt_uuid
}
struct Foo2 {
@@ -92,7 +94,8 @@
8: list<map<i16, i16>> maps,
9: list<list<i16>> lists,
10: list<set<i16>> sets,
- 11: list<Hello> hellos
+ 11: list<Hello> hellos,
+ 12: list<uuid> uuids
}
exception Xception {
@@ -119,6 +122,7 @@
8: i32 other_i32
9: SomeEnum some_enum;
10: map<SomeEnum, list<SomeEnum>> my_map;
+ 11: uuid unique_id;
}
struct Struct_with_union {
diff --git a/lib/rb/spec/binary_protocol_spec_shared.rb b/lib/rb/spec/binary_protocol_spec_shared.rb
index 4baedd7..c83f5ae 100644
--- a/lib/rb/spec/binary_protocol_spec_shared.rb
+++ b/lib/rb/spec/binary_protocol_spec_shared.rb
@@ -254,27 +254,43 @@
expect { @prot.write_binary(nil) }.to raise_error(StandardError, 'nil argument not allowed!')
end
+ it "should write a uuid" do
+ @prot.write_uuid("00112233-4455-6677-8899-aabbccddeeff")
+ a = @trans.read(@trans.available)
+ expect(a.unpack('C*')).to eq([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff])
+ end
+
+ it "should read a uuid" do
+ @trans.write([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff].pack('C*'))
+ uuid = @prot.read_uuid
+ expect(uuid).to eq("00112233-4455-6677-8899-aabbccddeeff")
+ end
+
+ it "should error gracefully when trying to write an invalid uuid" do
+ expect { @prot.write_uuid("invalid") }.to raise_error(Thrift::ProtocolException)
+ end
+
it "should write the message header without version when writes are not strict" do
@prot = protocol_class.new(@trans, true, false) # no strict write
@prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17)
expect(@trans.read(@trans.available)).to eq("\000\000\000\vtestMessage\001\000\000\000\021")
end
-
+
it "should write the message header with a version when writes are strict" do
@prot = protocol_class.new(@trans) # strict write
@prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17)
expect(@trans.read(@trans.available)).to eq("\200\001\000\001\000\000\000\vtestMessage\000\000\000\021")
end
-
+
# message footer is a noop
-
+
it "should read a field header" do
@trans.write([Thrift::Types::STRING, 3].pack("cn"))
expect(@prot.read_field_begin).to eq([nil, Thrift::Types::STRING, 3])
end
-
+
# field footer is a noop
-
+
it "should read a stop field" do
@trans.write([Thrift::Types::STOP].pack("c"));
expect(@prot.read_field_begin).to eq([nil, Thrift::Types::STOP, 0])
diff --git a/lib/rb/spec/compact_protocol_spec.rb b/lib/rb/spec/compact_protocol_spec.rb
index 513dd69..7519810 100644
--- a/lib/rb/spec/compact_protocol_spec.rb
+++ b/lib/rb/spec/compact_protocol_spec.rb
@@ -71,6 +71,30 @@
end
end
+ it "should write a uuid" do
+ trans = Thrift::MemoryBufferTransport.new
+ proto = Thrift::CompactProtocol.new(trans)
+
+ proto.write_uuid("00112233-4455-6677-8899-aabbccddeeff")
+ a = trans.read(trans.available)
+ expect(a.unpack('C*')).to eq([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff])
+ end
+
+ it "should read a uuid" do
+ trans = Thrift::MemoryBufferTransport.new
+ proto = Thrift::CompactProtocol.new(trans)
+
+ trans.write([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff].pack('C*'))
+ uuid = proto.read_uuid
+ expect(uuid).to eq("00112233-4455-6677-8899-aabbccddeeff")
+ end
+
+ it "should error gracefully when trying to write an invalid uuid" do
+ trans = Thrift::MemoryBufferTransport.new
+ proto = Thrift::CompactProtocol.new(trans)
+ expect { proto.write_uuid("invalid") }.to raise_error(Thrift::ProtocolException)
+ end
+
it "should encode and decode a monster struct correctly" do
trans = Thrift::MemoryBufferTransport.new
proto = Thrift::CompactProtocol.new(trans)
diff --git a/lib/rb/spec/json_protocol_spec.rb b/lib/rb/spec/json_protocol_spec.rb
index fe1af7b..9acaf86 100644
--- a/lib/rb/spec/json_protocol_spec.rb
+++ b/lib/rb/spec/json_protocol_spec.rb
@@ -252,6 +252,11 @@
expect(@trans.read(@trans.available)).to eq("\"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==\"")
end
+ it "should write a uuid" do
+ @prot.write_uuid("00112233-4455-6677-8899-aabbccddeeff")
+ expect(@trans.read(@trans.available)).to eq("\"00112233-4455-6677-8899-aabbccddeeff\"")
+ end
+
it "should get type name for type id" do
expect {@prot.get_type_name_for_type_id(Thrift::Types::STOP)}.to raise_error(NotImplementedError)
expect {@prot.get_type_name_for_type_id(Thrift::Types::VOID)}.to raise_error(NotImplementedError)
@@ -534,7 +539,17 @@
@trans.write("\"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==\"")
expect(@prot.read_binary.bytes.to_a).to eq((0...256).to_a)
end
-
+
+ it "should read a uuid" do
+ @trans.write("\"00112233-4455-6677-8899-aabbccddeeff\"")
+ expect(@prot.read_uuid).to eq("00112233-4455-6677-8899-aabbccddeeff")
+ end
+
+ it "should normalize uppercase uuid on read" do
+ @trans.write("\"00112233-4455-6677-8899-AABBCCDDEEFF\"")
+ expect(@prot.read_uuid).to eq("00112233-4455-6677-8899-aabbccddeeff")
+ end
+
it "should provide a reasonable to_s" do
expect(@prot.to_s).to eq("json(memory)")
end
diff --git a/lib/rb/spec/struct_spec.rb b/lib/rb/spec/struct_spec.rb
index 9a4baa8..9349686 100644
--- a/lib/rb/spec/struct_spec.rb
+++ b/lib/rb/spec/struct_spec.rb
@@ -289,5 +289,82 @@
e.write(prot)
end
end
+
+ it "should handle UUID fields in structs" do
+ struct = SpecNamespace::Foo.new(
+ simple: 42,
+ words: 'test',
+ opt_uuid: '550e8400-e29b-41d4-a716-446655440000'
+ )
+
+ trans = Thrift::MemoryBufferTransport.new
+ prot = Thrift::BinaryProtocol.new(trans)
+
+ struct.write(prot)
+
+ result = SpecNamespace::Foo.new
+ result.read(prot)
+
+ expect(result.simple).to eq(42)
+ expect(result.words).to eq('test')
+ expect(result.opt_uuid).to eq('550e8400-e29b-41d4-a716-446655440000')
+ end
+
+ it "should handle optional UUID fields when unset" do
+ struct = SpecNamespace::Foo.new(simple: 42, words: 'test')
+ expect(struct.opt_uuid).to be_nil
+ expect(struct.opt_uuid?).to be_falsey
+ end
+
+ it "should handle list of UUIDs in SimpleList" do
+ uuids = ['550e8400-e29b-41d4-a716-446655440000', '6ba7b810-9dad-11d1-80b4-00c04fd430c8']
+ struct = SpecNamespace::SimpleList.new(uuids: uuids)
+
+ trans = Thrift::MemoryBufferTransport.new
+ prot = Thrift::CompactProtocol.new(trans)
+
+ struct.write(prot)
+
+ result = SpecNamespace::SimpleList.new
+ result.read(prot)
+
+ expect(result.uuids).to eq(uuids)
+ end
+
+ it "should normalize UUID case to lowercase" do
+ struct = SpecNamespace::Foo.new(opt_uuid: '550E8400-E29B-41D4-A716-446655440000')
+
+ trans = Thrift::MemoryBufferTransport.new
+ prot = Thrift::BinaryProtocol.new(trans)
+
+ struct.write(prot)
+
+ result = SpecNamespace::Foo.new
+ result.read(prot)
+
+ expect(result.opt_uuid).to eq('550e8400-e29b-41d4-a716-446655440000')
+ end
+
+ it "should handle UUID alongside other types in SimpleList" do
+ struct = SpecNamespace::SimpleList.new(
+ bools: [true, false],
+ i32s: [1, 2, 3],
+ strings: ['hello', 'world'],
+ uuids: ['550e8400-e29b-41d4-a716-446655440000', '00000000-0000-0000-0000-000000000000']
+ )
+
+ trans = Thrift::MemoryBufferTransport.new
+ prot = Thrift::BinaryProtocol.new(trans)
+
+ struct.write(prot)
+
+ result = SpecNamespace::SimpleList.new
+ result.read(prot)
+
+ expect(result.bools).to eq([true, false])
+ expect(result.i32s).to eq([1, 2, 3])
+ expect(result.strings).to eq(['hello', 'world'])
+ expect(result.uuids).to eq(['550e8400-e29b-41d4-a716-446655440000', '00000000-0000-0000-0000-000000000000'])
+ end
end
end
diff --git a/lib/rb/spec/union_spec.rb b/lib/rb/spec/union_spec.rb
index efb3853..7bf4ca0 100644
--- a/lib/rb/spec/union_spec.rb
+++ b/lib/rb/spec/union_spec.rb
@@ -91,6 +91,24 @@
expect(union).not_to eq(other_union)
end
+ it "should equate two unions with the same UUID value" do
+ union = SpecNamespace::My_union.new(:unique_id, '550e8400-e29b-41d4-a716-446655440000')
+ other_union = SpecNamespace::My_union.new(:unique_id, '550e8400-e29b-41d4-a716-446655440000')
+ expect(union).to eq(other_union)
+ end
+
+ it "should not equate two unions with different UUID values" do
+ union = SpecNamespace::My_union.new(:unique_id, '550e8400-e29b-41d4-a716-446655440000')
+ other_union = SpecNamespace::My_union.new(:unique_id, '6ba7b810-9dad-11d1-80b4-00c04fd430c8')
+ expect(union).not_to eq(other_union)
+ end
+
+ it "should not equate UUID union with different field type" do
+ union = SpecNamespace::My_union.new(:unique_id, '550e8400-e29b-41d4-a716-446655440000')
+ other_union = SpecNamespace::My_union.new(:some_characters, '550e8400-e29b-41d4-a716-446655440000')
+ expect(union).not_to eq(other_union)
+ end
+
it "should inspect properly" do
union = SpecNamespace::My_union.new(:integer32, 25)
expect(union.inspect).to eq("<SpecNamespace::My_union integer32: 25>")
@@ -192,23 +210,58 @@
it "should be comparable" do
relationships = [
- [0, -1, -1, -1],
- [1, 0, -1, -1],
- [1, 1, 0, -1],
- [1, 1, 1, 0]]
+ [0, -1, -1, -1, -1, -1],
+ [1, 0, -1, -1, -1, -1],
+ [1, 1, 0, -1, -1, -1],
+ [1, 1, 1, 0, -1, -1],
+ [1, 1, 1, 1, 0, -1],
+ [1, 1, 1, 1, 1, 0]]
objs = [
SpecNamespace::TestUnion.new(:string_field, "blah"),
SpecNamespace::TestUnion.new(:string_field, "blahblah"),
SpecNamespace::TestUnion.new(:i32_field, 1),
+ SpecNamespace::TestUnion.new(:uuid_field, '550e8400-e29b-41d4-a716-446655440000'),
+ SpecNamespace::TestUnion.new(:uuid_field, '6ba7b810-9dad-11d1-80b4-00c04fd430c8'),
SpecNamespace::TestUnion.new()]
- for y in 0..3
- for x in 0..3
+ objs.size.times do |y|
+ objs.size.times do |x|
# puts "#{objs[y].inspect} <=> #{objs[x].inspect} should == #{relationships[y][x]}"
expect(objs[y] <=> objs[x]).to eq(relationships[y][x])
end
end
end
+
+ it "should handle UUID as union value" do
+ union = SpecNamespace::My_union.new
+ union.unique_id = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
+
+ trans = Thrift::MemoryBufferTransport.new
+ prot = Thrift::CompactProtocol.new(trans)
+
+ union.write(prot)
+
+ result = SpecNamespace::My_union.new
+ result.read(prot)
+
+ expect(result.unique_id).to eq('ffffffff-ffff-ffff-ffff-ffffffffffff')
+ expect(result.get_set_field).to eq(:unique_id)
+ end
+
+ it "should normalize UUID case in union" do
+ union = SpecNamespace::My_union.new
+ union.unique_id = '550E8400-E29B-41D4-A716-446655440000'
+
+ trans = Thrift::MemoryBufferTransport.new
+ prot = Thrift::BinaryProtocol.new(trans)
+
+ union.write(prot)
+
+ result = SpecNamespace::My_union.new
+ result.read(prot)
+
+ expect(result.unique_id).to eq('550e8400-e29b-41d4-a716-446655440000')
+ end
end
end
diff --git a/lib/rb/spec/uuid_validation_spec.rb b/lib/rb/spec/uuid_validation_spec.rb
new file mode 100644
index 0000000..c95d408
--- /dev/null
+++ b/lib/rb/spec/uuid_validation_spec.rb
@@ -0,0 +1,238 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require 'spec_helper'
+
+describe 'UUID Validation' do
+ protocols = [
+ ['BinaryProtocol', Thrift::BinaryProtocol],
+ ]
+
+ if defined?(Thrift::BinaryProtocolAccelerated)
+ protocols << ['BinaryProtocolAccelerated', Thrift::BinaryProtocolAccelerated]
+ end
+
+ protocols << ['CompactProtocol', Thrift::CompactProtocol]
+ protocols << ['JsonProtocol', Thrift::JsonProtocol]
+
+ protocols.each do |protocol_name, protocol_class|
+ describe protocol_name do
+ before(:each) do
+ @trans = Thrift::MemoryBufferTransport.new
+ @prot = protocol_class.new(@trans)
+ end
+
+ context 'valid UUIDs' do
+ it 'should accept lowercase UUIDs' do
+ uuid = '550e8400-e29b-41d4-a716-446655440000'
+ expect { @prot.write_uuid(uuid) }.not_to raise_error
+ result = @prot.read_uuid
+ expect(result).to eq(uuid)
+ end
+
+ it 'should accept uppercase UUIDs' do
+ uuid = '550E8400-E29B-41D4-A716-446655440000'
+ expect { @prot.write_uuid(uuid) }.not_to raise_error
+ result = @prot.read_uuid
+ # Result should be lowercase
+ expect(result).to eq('550e8400-e29b-41d4-a716-446655440000')
+ end
+
+ it 'should accept mixed case UUIDs' do
+ uuid = '550e8400-E29B-41d4-A716-446655440000'
+ expect { @prot.write_uuid(uuid) }.not_to raise_error
+ result = @prot.read_uuid
+ expect(result).to eq('550e8400-e29b-41d4-a716-446655440000')
+ end
+
+ it 'should accept all zeros' do
+ uuid = '00000000-0000-0000-0000-000000000000'
+ expect { @prot.write_uuid(uuid) }.not_to raise_error
+ result = @prot.read_uuid
+ expect(result).to eq(uuid)
+ end
+
+ it 'should accept all fs' do
+ uuid = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
+ expect { @prot.write_uuid(uuid) }.not_to raise_error
+ result = @prot.read_uuid
+ expect(result).to eq(uuid)
+ end
+ end
+
+ context 'invalid UUIDs' do
+ def expect_invalid_uuid(value, message)
+ expect { @prot.write_uuid(value) }.to raise_error(Thrift::ProtocolException) do |error|
+ expect(error.type).to eq(Thrift::ProtocolException::INVALID_DATA)
+ expect(error.message).to eq(message)
+ end
+ end
+
+ it 'should reject nil' do
+ expect_invalid_uuid(nil, 'UUID must be a string')
+ end
+
+ it 'should reject non-string' do
+ expect_invalid_uuid(12345, 'UUID must be a string')
+ end
+
+ it 'should reject wrong length' do
+ expect_invalid_uuid('550e8400-e29b-41d4-a716', 'Invalid UUID format')
+ end
+
+ it 'should reject missing hyphens' do
+ expect_invalid_uuid('550e8400e29b41d4a716446655440000', 'Invalid UUID format')
+ end
+
+ it 'should reject hyphens in wrong positions' do
+ expect_invalid_uuid('550e840-0e29b-41d4-a716-446655440000', 'Invalid UUID format')
+ end
+
+ it 'should reject invalid hex characters (g)' do
+ expect_invalid_uuid('550e8400-e29b-41d4-a716-44665544000g', 'Invalid UUID format')
+ end
+
+ it 'should reject invalid hex characters (z)' do
+ expect_invalid_uuid('z50e8400-e29b-41d4-a716-446655440000', 'Invalid UUID format')
+ end
+
+ it 'should reject invalid hex characters (space)' do
+ expect_invalid_uuid('550e8400-e29b-41d4-a716-44665544000 ', 'Invalid UUID format')
+ end
+
+ it 'should reject empty string' do
+ expect_invalid_uuid('', 'Invalid UUID format')
+ end
+
+ it 'should reject UUID with extra characters' do
+ expect_invalid_uuid('550e8400-e29b-41d4-a716-446655440000x', 'Invalid UUID format')
+ end
+
+ it 'should reject trailing hyphen' do
+ expect_invalid_uuid('550e8400-e29b-41d4-a716-44665544000-', 'Invalid UUID format')
+ end
+
+ it 'should reject hyphen inside hex pair' do
+ expect_invalid_uuid('550e8400-e29b-41d4-a716-4466-5544000', 'Invalid UUID format')
+ end
+ end
+
+ context 'malformed binary data on read' do
+ it 'should raise error on truncated data' do
+ @trans = Thrift::MemoryBufferTransport.new
+ @prot = protocol_class.new(@trans)
+
+ # Write only 10 bytes instead of 16
+ if protocol_class == Thrift::JsonProtocol
+ @trans.write('"00000000-0000-0000-0000"')
+ else
+ @trans.write("\x00" * 10)
+ end
+
+ expect { @prot.read_uuid }.to raise_error(EOFError)
+ end
+
+ it 'should raise error on 15 bytes (one byte short)' do
+ @trans = Thrift::MemoryBufferTransport.new
+ @prot = protocol_class.new(@trans)
+
+ if protocol_class == Thrift::JsonProtocol
+ @trans.write('"00000000-0000-0000-0000-000000000"')
+ else
+ @trans.write("\x00" * 15)
+ end
+
+ expect { @prot.read_uuid }.to raise_error(EOFError)
+ end
+
+ it 'should raise error on empty buffer' do
+ @trans = Thrift::MemoryBufferTransport.new
+ @prot = protocol_class.new(@trans)
+
+ expect { @prot.read_uuid }.to raise_error(EOFError)
+ end
+ end
+
+ context 'multiple UUIDs in sequence' do
+ it 'should handle 10 UUIDs in sequence' do
+ uuids = 10.times.map { |i| sprintf('%08x-0000-0000-0000-000000000000', i) }
+
+ @trans = Thrift::MemoryBufferTransport.new
+ @prot = protocol_class.new(@trans)
+
+ uuids.each { |uuid| @prot.write_uuid(uuid) }
+
+ results = 10.times.map { @prot.read_uuid }
+ expect(results).to eq(uuids)
+ end
+
+ it 'should handle UUIDs interleaved with other types' do
+ @trans = Thrift::MemoryBufferTransport.new
+ @prot = protocol_class.new(@trans)
+
+ @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 0)
+ @prot.write_i32(42)
+ @prot.write_uuid('550e8400-e29b-41d4-a716-446655440000')
+ @prot.write_string('test')
+ @prot.write_uuid('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
+ @prot.write_i64(123456789)
+ @prot.write_message_end
+
+ @prot.read_message_begin
+ expect(@prot.read_i32).to eq(42)
+ expect(@prot.read_uuid).to eq('550e8400-e29b-41d4-a716-446655440000')
+ expect(@prot.read_string).to eq('test')
+ expect(@prot.read_uuid).to eq('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
+ expect(@prot.read_i64).to eq(123456789)
+ @prot.read_message_end
+ end
+
+ it 'should handle UUIDs in struct fields context' do
+ @trans = Thrift::MemoryBufferTransport.new
+ @prot = protocol_class.new(@trans)
+
+ # Simulate struct field headers
+ @prot.write_struct_begin('test')
+ @prot.write_field_begin('uuid1', Thrift::Types::UUID, 1)
+ @prot.write_uuid('550e8400-e29b-41d4-a716-446655440000')
+ @prot.write_field_end
+ @prot.write_field_begin('uuid2', Thrift::Types::UUID, 2)
+ @prot.write_uuid('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
+ @prot.write_field_end
+ @prot.write_field_stop
+ @prot.write_struct_end
+
+ @prot.read_struct_begin
+ name, type, id = @prot.read_field_begin
+ expect(type).to eq(Thrift::Types::UUID)
+ expect(@prot.read_uuid).to eq('550e8400-e29b-41d4-a716-446655440000')
+ @prot.read_field_end
+
+ name, type, id = @prot.read_field_begin
+ expect(type).to eq(Thrift::Types::UUID)
+ expect(@prot.read_uuid).to eq('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
+ @prot.read_field_end
+
+ name, type, id = @prot.read_field_begin
+ expect(type).to eq(Thrift::Types::STOP)
+ end
+ end
+ end
+ end
+end