From 97dcdf2636f9f718c4bb9040ec866635004ac13b Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Sat, 29 Aug 2015 10:46:51 -0500 Subject: [PATCH 01/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Added t_dart_generator.cc and add as a target for Make. --- compiler/cpp/CMakeLists.txt | 3 +- compiler/cpp/Makefile.am | 1 + compiler/cpp/src/generate/t_dart_generator.cc | 2333 +++++++++++++++++ 3 files changed, 2336 insertions(+), 1 deletion(-) create mode 100644 compiler/cpp/src/generate/t_dart_generator.cc diff --git a/compiler/cpp/CMakeLists.txt b/compiler/cpp/CMakeLists.txt index bc6591ca884..2d0c3f08b35 100644 --- a/compiler/cpp/CMakeLists.txt +++ b/compiler/cpp/CMakeLists.txt @@ -47,7 +47,7 @@ set(libparse_SOURCES add_library(libparse STATIC ${libparse_SOURCES}) # Create the thrift compiler -set( thrift_SOURCES +set( thrift_SOURCES src/main.cc src/md5.c src/generate/t_generator.cc @@ -100,6 +100,7 @@ THRIFT_ADD_COMPILER(c_glib "Enable compiler for C with Glib" ON) THRIFT_ADD_COMPILER(cpp "Enable compiler for C++" ON) THRIFT_ADD_COMPILER(java "Enable compiler for Java" ON) THRIFT_ADD_COMPILER(as3 "Enable compiler for ActionScript 3" ON) +THRIFT_ADD_COMPILER(dart "Enable compiler for Dart" ON) THRIFT_ADD_COMPILER(haxe "Enable compiler for Haxe" ON) THRIFT_ADD_COMPILER(csharp "Enable compiler for C#" ON) THRIFT_ADD_COMPILER(py "Enable compiler for Python 2.0" ON) diff --git a/compiler/cpp/Makefile.am b/compiler/cpp/Makefile.am index 485e30ccc77..958cbef6c0c 100644 --- a/compiler/cpp/Makefile.am +++ b/compiler/cpp/Makefile.am @@ -73,6 +73,7 @@ thrift_SOURCES += src/generate/t_c_glib_generator.cc \ src/generate/t_java_generator.cc \ src/generate/t_json_generator.cc \ src/generate/t_as3_generator.cc \ + src/generate/t_dart_generator.cc \ src/generate/t_haxe_generator.cc \ src/generate/t_csharp_generator.cc \ src/generate/t_py_generator.cc \ diff --git a/compiler/cpp/src/generate/t_dart_generator.cc b/compiler/cpp/src/generate/t_dart_generator.cc new file mode 100644 index 00000000000..ad1a01b4458 --- /dev/null +++ b/compiler/cpp/src/generate/t_dart_generator.cc @@ -0,0 +1,2333 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include + +#include "platform.h" +#include "t_oop_generator.h" + +using std::map; +using std::ofstream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes +static const string endl2 = "\n\n"; + +/* forward declarations */ +string initial_caps_to_underscores(string name); + +/** + * Dart code generator + * + */ +class t_dart_generator : public t_oop_generator { +public: + t_dart_generator(t_program* program, + const std::map& parsed_options, + const std::string& option_string) + : t_oop_generator(program) { + (void)option_string; + std::map::const_iterator iter; + + iter = parsed_options.find("library_name"); + if (iter != parsed_options.end()) { + library_name_ = (iter->second); + } else { + library_name_ = ""; + } + + out_dir_base_ = "gen-dart"; + } + + void scope_up(std::ostream& out, std::string prefix=" ") { + out << prefix << "{" << endl; + indent_up(); + } + + void scope_down(std::ostream& out, std::string postfix=endl) { + indent_down(); + indent(out) << "}" << postfix; + } + + string replace_all(string contents, string search, string repl) { + string str(contents); + + size_t slen = search.length(); + size_t rlen = repl.length(); + size_t incr = (rlen > 0) ? rlen : 1; + + if (slen > 0) { + size_t found = str.find(search); + while ((found != string::npos) && (found < str.length())) { + str.replace(found, slen, repl); + found = str.find(search, found + incr); + } + } + + return str; + } + + + /** + * Init and close methods + */ + + void init_generator(); + void close_generator(); + + void export_class_to_library(string file_name, string class_name); + + void generate_dart_library(); + + void generate_consts(std::vector consts); + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef); + void generate_enum(t_enum* tenum); + void generate_struct(t_struct* tstruct); + void generate_xception(t_struct* txception); + void generate_service(t_service* tservice); + + void print_const_value(std::ofstream& out, + std::string name, + t_type* type, + t_const_value* value, + bool in_static, + bool defval = false); + std::string render_const_value(ofstream& out, + std::string name, + t_type* type, + t_const_value* value); + + /** + * Service-level generation functions + */ + + void generate_dart_struct(t_struct* tstruct, bool is_exception); + + void generate_dart_struct_definition(std::ofstream& out, + t_struct* tstruct, + bool is_xception = false, + bool is_result = false, + string export_file_name = ""); + void generate_dart_struct_reader(std::ofstream& out, t_struct* tstruct); + void generate_dart_validator(std::ofstream& out, t_struct* tstruct); + void generate_dart_struct_result_writer(std::ofstream& out, t_struct* tstruct); + void generate_dart_struct_writer(std::ofstream& out, t_struct* tstruct); + void generate_dart_struct_tostring(std::ofstream& out, t_struct* tstruct); + std::string get_dart_type_string(t_type* type); + void generate_generic_field_getters(std::ofstream& out, t_struct* tstruct); + void generate_generic_field_setters(std::ofstream& out, t_struct* tstruct); + void generate_generic_isset_method(std::ofstream& out, t_struct* tstruct); + void generate_dart_bean_boilerplate(std::ofstream& out, t_struct* tstruct); + + void generate_function_helpers(t_function* tfunction); + std::string get_cap_name(std::string name); + std::string get_field_name(std::string name); + std::string get_file_name(std::string name); + std::string generate_isset_check(t_field* field); + std::string generate_isset_check(std::string field); + void generate_isset_set(ofstream& out, t_field* field); + + void generate_service_interface(t_service* tservice); + void generate_service_helpers(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_server(t_service* tservice); + void generate_process_function(t_service* tservice, t_function* tfunction); + + /** + * Serialization constructs + */ + + void generate_deserialize_field(std::ofstream& out, t_field* tfield, std::string prefix = ""); + + void generate_deserialize_struct(std::ofstream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_deserialize_container(std::ofstream& out, t_type* ttype, std::string prefix = ""); + + void generate_deserialize_set_element(std::ofstream& out, t_set* tset, std::string prefix = ""); + + void generate_deserialize_map_element(std::ofstream& out, t_map* tmap, std::string prefix = ""); + + void generate_deserialize_list_element(std::ofstream& out, + t_list* tlist, + std::string prefix = ""); + + void generate_serialize_field(std::ofstream& out, t_field* tfield, std::string prefix = ""); + + void generate_serialize_struct(std::ofstream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_serialize_container(std::ofstream& out, t_type* ttype, std::string prefix = ""); + + void generate_serialize_map_element(std::ofstream& out, + t_map* tmap, + std::string iter, + std::string map); + + void generate_serialize_set_element(std::ofstream& out, t_set* tmap, std::string iter); + + void generate_serialize_list_element(std::ofstream& out, t_list* tlist, std::string iter); + + void generate_dart_doc(std::ofstream& out, t_doc* tdoc); + + void generate_dart_doc(std::ofstream& out, t_function* tdoc); + + /** + * Helper rendering functions + */ + + std::string dart_library(string file_name); + std::string dart_async_imports(); + std::string dart_thrift_imports(); + std::string dart_thrift_gen_imports(t_struct* tstruct, string& imports); + std::string dart_thrift_gen_imports(t_service* tservice); + std::string type_name(t_type* ttype, bool in_container = false, bool in_init = false); + std::string base_type_name(t_base_type* tbase, bool in_container = false); + std::string declare_field(t_field* tfield, bool init = false); + std::string function_signature(t_function* tfunction); + std::string argument_list(t_struct* tstruct); + std::string type_to_enum(t_type* ttype); + std::string get_enum_class_name(t_type* type); + + bool type_can_be_null(t_type* ttype) { + ttype = get_true_type(ttype); + + return ttype->is_container() || ttype->is_struct() || ttype->is_xception() + || ttype->is_string(); + } + + std::string constant_name(std::string name); + +private: + /** + * File streams + */ + std::ofstream f_service_; + + std::string library_name_; + std::string base_dir_; + std::string src_dir_; + std::string library_exports_; +}; + +/** + * Prepares for file generation by opening up the necessary file output + * streams. + * + * @param tprogram The program to generate + */ +void t_dart_generator::init_generator() { + MKDIR(get_out_dir().c_str()); + + if (library_name_.empty()) { + library_name_ = program_->get_namespace("dart"); + } + if (library_name_.empty()) { + library_name_ = "my_library"; + } + library_name_ = replace_all(library_name_, ".", "_"); + + string subdir = get_out_dir() + "/" + library_name_; + MKDIR(subdir.c_str()); + + base_dir_ = subdir; + subdir += "/lib"; + MKDIR(subdir.c_str()); + + subdir += "/src"; + MKDIR(subdir.c_str()); + src_dir_ = subdir; +} + +/** + * The Dart library + * + * @return String of the library, e.g. "library myservice;" + */ +string t_dart_generator::dart_library(string file_name) { + string out = "library "; + if (!library_name_.empty()) { + out += library_name_; + } + if (!file_name.empty()) { + out += ".src." + file_name; + } + return out + ";\n"; +} + +/** + * Prints standard Dart imports + * + * @return List of imports for Dart types that are used in here + */ +string t_dart_generator::dart_async_imports() { + return "import 'dart:async';" + endl; +} + +/** + * Prints standard dart imports + * + * @return List of imports necessary for thrift + */ +string t_dart_generator::dart_thrift_imports() { + return string() + + "import 'package:thrift/thrift.dart';" + endl + + "import 'package:" + library_name_ + "/" + library_name_ + ".dart';" + endl; +} + +/** + * Prints imports needed for a given type + * + * @return List of imports necessary for a given t_struct + */ +string t_dart_generator::dart_thrift_gen_imports(t_struct* tstruct, string& imports) { + + const vector& members = tstruct->get_members(); + vector::const_iterator m_iter; + + // For each type check if it is from a differnet namespace + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_program* program = (*m_iter)->get_type()->get_program(); + if (program != NULL && program != program_) { + if (!library_name_.empty()) { + if (imports.find(library_name_ + "." + (*m_iter)->get_type()->get_name()) == string::npos) { + imports.append("import " + library_name_ + "." + (*m_iter)->get_type()->get_name() + ";\n"); + } + } + } + } + return imports; +} + +/** + * Prints imports needed for a given type + * + * @return List of imports necessary for a given t_service + */ +string t_dart_generator::dart_thrift_gen_imports(t_service* tservice) { + string imports; + const vector& functions = tservice->get_functions(); + vector::const_iterator f_iter; + + // For each type check if it is from a differnet namespace + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_program* program = (*f_iter)->get_returntype()->get_program(); + if (program != NULL && program != program_) { + if (!library_name_.empty()) { + if (imports.find(library_name_ + "." + (*f_iter)->get_returntype()->get_name()) == string::npos) { + imports.append("import '" + library_name_ + "." + (*f_iter)->get_returntype()->get_name() + + "';\n"); + } + } + } + + dart_thrift_gen_imports((*f_iter)->get_arglist(), imports); + dart_thrift_gen_imports((*f_iter)->get_xceptions(), imports); + } + + return imports; +} + +/** + * Not used + */ +void t_dart_generator::close_generator() { + generate_dart_library(); +} + +void t_dart_generator::generate_dart_library() { + string f_library_name_ = base_dir_ + "/lib/" + library_name_ + ".dart"; + ofstream f_library; + f_library.open(f_library_name_.c_str()); + + f_library << autogen_comment() << endl; + f_library << "library " << library_name_ << ";" << endl2; + f_library << library_exports_; + + f_library.close(); +} + +void t_dart_generator::export_class_to_library(string file_name, string class_name) { + library_exports_ += "export 'src/" + file_name + ".dart' show " + class_name + ";" + endl; +} + +/** + * Not used + * + * @param ttypedef The type definition + */ +void t_dart_generator::generate_typedef(t_typedef* ttypedef) { + (void)ttypedef; +} + +/** + * Enums are a class with a set of static constants. + * + * @param tenum The enumeration + */ +void t_dart_generator::generate_enum(t_enum* tenum) { + // Make output file + string file_name = get_file_name(tenum->get_name()); + + string f_enum_name = src_dir_ + "/" + file_name + ".dart"; + ofstream f_enum; + f_enum.open(f_enum_name.c_str()); + + // Comment and add library + f_enum << autogen_comment() << dart_library(file_name) << endl; + + string class_name = tenum->get_name(); + export_class_to_library(file_name, class_name); + f_enum << "class " << class_name; + scope_up(f_enum); + + vector constants = tenum->get_constants(); + vector::iterator c_iter; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + indent(f_enum) << "static const int " << (*c_iter)->get_name() << " = " << value << ";" + << endl; + } + + // Create a static Set with all valid values for this enum + f_enum << endl; + + indent(f_enum) << "static final VALID_VALUES:Set = new Set([" << endl; + indent_up(); + bool firstValue = true; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + // populate set + indent(f_enum) << (firstValue ? "" : ", "); + f_enum << (*c_iter)->get_name() << endl; + firstValue = false; + } + indent_down(); + indent(f_enum) << "]);" << endl; + + indent(f_enum) << "static const VALUES_TO_NAMES:Map = new Map({" << endl; + indent_up(); + firstValue = true; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + indent(f_enum) << (firstValue ? "" : ", "); + f_enum << (*c_iter)->get_name() << ": '" << (*c_iter)->get_name() << "'" << endl; + firstValue = false; + } + indent_down(); + indent(f_enum) << "});" << endl; + + scope_down(f_enum); // end class + + f_enum.close(); +} + +/** + * Generates a class that holds all the constants. + */ +void t_dart_generator::generate_consts(std::vector consts) { + if (consts.empty()) { + return; + } + + string class_name = program_name_ + "Constants"; + string file_name = get_file_name(class_name); + + string f_consts_name = src_dir_ + "/" + file_name + ".dart"; + ofstream f_consts; + f_consts.open(f_consts_name.c_str()); + + // Print header + f_consts << autogen_comment() << dart_library(file_name) << endl; + + export_class_to_library(file_name, class_name); + indent(f_consts) << "class " << class_name; + scope_up(f_consts); + + vector::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + print_const_value(f_consts, + (*c_iter)->get_name(), + (*c_iter)->get_type(), + (*c_iter)->get_value(), + false); + } + + scope_down(f_consts); + + f_consts.close(); +} + +void t_dart_generator::print_const_value(std::ofstream& out, + string name, + t_type* type, + t_const_value* value, + bool in_static, + bool defval) { + type = get_true_type(type); + + indent(out); + if (!defval) { + out << (in_static ? "var " : "static final "); + } + if (type->is_base_type()) { + if (!defval) { + out << type_name(type); + } + string v2 = render_const_value(out, name, type, value); + out << name; + out << " = " << v2 << ";" << endl << endl; + } else if (type->is_enum()) { + if (!defval) { + out << type_name(type); + } + out << name; + out << " = " << value->get_integer() << ";" << endl << endl; + } else if (type->is_struct() || type->is_xception()) { + const vector& fields = ((t_struct*)type)->get_members(); + vector::const_iterator f_iter; + const map& val = value->get_map(); + map::const_iterator v_iter; + out << type_name(type) << name << " = new " << type_name(type, false, true) << "();" + << endl; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + string val = render_const_value(out, name, field_type, v_iter->second); + indent(out) << name << "."; + out << v_iter->first->get_string() << " = " << val << ";" << endl; + } + out << endl; + } else if (type->is_map()) { + if (!defval) { + out << type_name(type); + } + out << name; + out << " = new " << type_name(type, false, true) << "();" << endl; + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + const map& val = value->get_map(); + map::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string key = render_const_value(out, name, ktype, v_iter->first); + string val = render_const_value(out, name, vtype, v_iter->second); + indent(out) << name << "[" << key << "] = " << val << ";" << endl; + } + out << endl; + } else if (type->is_list() || type->is_set()) { + if (!defval) { + out << type_name(type); + } + out << name; + out << " = new " << type_name(type, false, true) << "();" << endl; + t_type* etype; + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + const vector& val = value->get_list(); + vector::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string val = render_const_value(out, name, etype, *v_iter); + indent(out) << name << "." << (type->is_list() ? "push" : "add") << "(" << val << ");" + << endl; + } + out << endl; + } else { + throw "compiler error: no const of type " + type->get_name(); + } +} + +string t_dart_generator::render_const_value(ofstream& out, + string name, + t_type* type, + t_const_value* value) { + (void)name; + type = get_true_type(type); + std::ostringstream render; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + render << "'" << get_escaped_string(value) << "'"; + break; + case t_base_type::TYPE_BOOL: + render << ((value->get_integer() > 0) ? "true" : "false"); + break; + case t_base_type::TYPE_BYTE: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + render << value->get_integer(); + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + render << value->get_integer(); + } else { + render << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + render << value->get_integer(); + } else { + string t = tmp("tmp"); + print_const_value(out, t, type, value, true); + render << t; + } + + return render.str(); +} + +/** + * Generates a struct definition for a thrift data type. This is a class + * with data members, read(), write(), and an inner Isset class. + * + * @param tstruct The struct definition + */ +void t_dart_generator::generate_struct(t_struct* tstruct) { + generate_dart_struct(tstruct, false); +} + +/** + * Exceptions are structs, but they inherit from Exception + * + * @param tstruct The struct definition + */ +void t_dart_generator::generate_xception(t_struct* txception) { + generate_dart_struct(txception, true); +} + +/** + * Dart struct definition. + * + * @param tstruct The struct definition + */ +void t_dart_generator::generate_dart_struct(t_struct* tstruct, bool is_exception) { + string file_name = get_file_name(tstruct->get_name()); + string f_struct_name = src_dir_ + "/" + file_name + ".dart"; + ofstream f_struct; + f_struct.open(f_struct_name.c_str()); + + f_struct << autogen_comment() << dart_library(file_name) << endl; + + string imports; + + f_struct << dart_thrift_imports() << dart_thrift_gen_imports(tstruct, imports) << endl; + + generate_dart_struct_definition(f_struct, tstruct, is_exception, false, file_name); + + f_struct.close(); +} + +/** + * Dart struct definition. This has various parameters, as it could be + * generated standalone or inside another class as a helper. If it + * is a helper than it is a static class. + * + * @param tstruct The struct definition + * @param is_exception Is this an exception? + * @param in_class If inside a class, needs to be static class + * @param is_result If this is a result it needs a different writer + */ +void t_dart_generator::generate_dart_struct_definition(ofstream& out, + t_struct* tstruct, + bool is_exception, + bool is_result, + string export_file_name) { + generate_dart_doc(out, tstruct); + + string class_name = tstruct->get_name(); + if (!export_file_name.empty()) { + export_class_to_library(export_file_name, class_name); + } + indent(out) << "class " << class_name << " "; + + if (is_exception) { + out << "extends Error "; + } + out << "implements TBase"; + scope_up(out); + + indent(out) << "static final TStruct _STRUCT_DESC = new TStruct(\"" << tstruct->get_name() + << "\");" << endl; + + // Members are public for -dart, private for -dartbean + const vector& members = tstruct->get_members(); + vector::const_iterator m_iter; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + indent(out) << "static final TField _" << constant_name((*m_iter)->get_name()) + << "_FIELD_DESC = new TField(\"" << (*m_iter)->get_name() << "\", " + << type_to_enum((*m_iter)->get_type()) << ", " << (*m_iter)->get_key() << ");" + << endl; + } + + out << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + generate_dart_doc(out, *m_iter); + indent(out) << type_name((*m_iter)->get_type()) + " _" + << get_field_name((*m_iter)->get_name()) << ";" << endl; + + indent(out) << "static const int " << upcase_string((*m_iter)->get_name()) + << " = " << (*m_iter)->get_key() << ";" << endl; + } + + out << endl; + + // Inner Isset class + if (members.size() > 0) { + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (!type_can_be_null((*m_iter)->get_type())) { + string field_name = get_field_name((*m_iter)->get_name()); + indent(out) << "bool __isset_" << field_name << " = false;" << endl; + } + } + } + + out << endl; + + // Default constructor + indent(out) << tstruct->get_name() << "()"; + scope_up(out); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if ((*m_iter)->get_value() != NULL) { + indent(out) << "this._" << get_field_name((*m_iter)->get_name()) << " = " + << (*m_iter)->get_value()->get_integer() << ";" << endl; + } + } + scope_down(out); + out << endl; + + generate_dart_bean_boilerplate(out, tstruct); + generate_generic_field_getters(out, tstruct); + generate_generic_field_setters(out, tstruct); + generate_generic_isset_method(out, tstruct); + + generate_dart_struct_reader(out, tstruct); + if (is_result) { + generate_dart_struct_result_writer(out, tstruct); + } else { + generate_dart_struct_writer(out, tstruct); + } + generate_dart_struct_tostring(out, tstruct); + generate_dart_validator(out, tstruct); + scope_down(out); + out << endl; +} + +/** + * Generates a function to read all the fields of the struct. + * + * @param tstruct The struct definition + */ +void t_dart_generator::generate_dart_struct_reader(ofstream& out, t_struct* tstruct) { + indent(out) << "read(TProtocol iprot)"; + scope_up(out); + + const vector& fields = tstruct->get_members(); + vector::const_iterator f_iter; + + // Declare stack tmp variables and read struct header + indent(out) << "TField field;" << endl; + indent(out) << "iprot.readStructBegin();" << endl; + + // Loop over reading in fields + indent(out) << "while (true)"; + scope_up(out); + + // Read beginning field marker + indent(out) << "field = iprot.readFieldBegin();" << endl; + + // Check for field STOP marker and break + indent(out) << "if (field.type == TType.STOP)"; + scope_up(out); + indent(out) << "break;" << endl; + scope_down(out); + + // Switch statement on the field we are reading + indent(out) << "switch (field.id)"; + scope_up(out); + + // Generate deserialization code for known cases + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent(out) << "case " << upcase_string((*f_iter)->get_name()) << ":" << endl; + indent_up(); + + indent(out) << "if (field.type == " << type_to_enum((*f_iter)->get_type()) << ")"; + scope_up(out); + + generate_deserialize_field(out, *f_iter, "this."); + generate_isset_set(out, *f_iter); + + scope_down(out, " else"); + scope_up(out); + indent(out) << "TProtocolUtil.skip(iprot, field.type);" << endl; + scope_down(out); + + indent(out) << "break;" << endl; + indent_down(); + } + + // In the default case we skip the field + indent(out) << "default:" << endl; + indent_up(); + indent(out) << "TProtocolUtil.skip(iprot, field.type);" << endl; + indent(out) << "break;" << endl; + indent_down(); + + scope_down(out); + + // Read field end marker + indent(out) << "iprot.readFieldEnd();" << endl; + + scope_down(out); + + indent(out) << "iprot.readStructEnd();" << endl2; + + // in non-beans style, check for required fields of primitive type + // (which can be checked here but not in the general validate method) + indent(out) << "// check for required fields of primitive type, which can't be " + "checked in the validate method" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED && !type_can_be_null((*f_iter)->get_type())) { + string field_name = get_field_name((*f_iter)->get_name()); + indent(out) << "if (!__isset_" << field_name << ")"; + scope_up(out); + indent(out) << " throw new TProtocolError(TProtocolError.UNKNOWN, \"Required field '" + << field_name + << "' was not found in serialized data! Struct: \" + toString());" << endl; + scope_down(out, endl2); + } + } + + // performs various checks (e.g. check that all required fields are set) + indent(out) << "validate();" << endl; + + scope_down(out, endl2); +} + +// generates dart method to perform various checks +// (e.g. check that all required fields are set) +void t_dart_generator::generate_dart_validator(ofstream& out, t_struct* tstruct) { + indent(out) << "validate()"; + scope_up(out); + + const vector& fields = tstruct->get_members(); + vector::const_iterator f_iter; + + indent(out) << "// check for required fields" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED) { + string field_name = get_field_name((*f_iter)->get_name()); + if (type_can_be_null((*f_iter)->get_type())) { + indent(out) << "if (" << field_name << " == null)"; + scope_up(out); + indent(out) << "throw new TProtocolError(TProtocolError.UNKNOWN, \"Required field '" + << field_name << "' was not present! Struct: \" + toString());" + << endl; + scope_down(out); + } else { + indent(out) << "// alas, we cannot check '" << field_name + << "' because it's a primitive and you chose the non-beans generator." << endl; + } + } + } + + // check that fields of type enum have valid values + indent(out) << "// check that fields of type enum have valid values" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = (*f_iter); + t_type* type = field->get_type(); + // if field is an enum, check that its value is valid + if (type->is_enum()) { + string field_name = get_field_name(field->get_name()); + indent(out) << "if (" << generate_isset_check(field) << " && !" << get_enum_class_name(type) + << ".VALID_VALUES.contains(" << field_name << "))"; + scope_up(out); + indent(out) << "throw new TProtocolError(TProtocolError.UNKNOWN, \"The field '" + << field_name << "' has been assigned the invalid value \" + " + << field_name << ");" << endl; + scope_down(out); + } + } + + scope_down(out, endl2); +} + +/** + * Generates a function to write all the fields of the struct + * + * @param tstruct The struct definition + */ +void t_dart_generator::generate_dart_struct_writer(ofstream& out, t_struct* tstruct) { + out << indent() << "write(TProtocol oprot)"; + scope_up(out); + + string name = tstruct->get_name(); + const vector& fields = tstruct->get_sorted_members(); + vector::const_iterator f_iter; + + // performs various checks (e.g. check that all required fields are set) + indent(out) << "validate();" << endl2; + + indent(out) << "oprot.writeStructBegin(_STRUCT_DESC);" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + string field_name = get_field_name((*f_iter)->get_name()); + bool null_allowed = type_can_be_null((*f_iter)->get_type()); + if (null_allowed) { + indent(out) << "if (this." << field_name << " != null)"; + scope_up(out); + } + + indent(out) << "oprot.writeFieldBegin(_" << constant_name((*f_iter)->get_name()) + << "_FIELD_DESC);" << endl; + + // Write field contents + generate_serialize_field(out, *f_iter, "this."); + + // Write field closer + indent(out) << "oprot.writeFieldEnd();" << endl; + + if (null_allowed) { + scope_down(out); + } + } + // Write the struct map + indent(out) << "oprot.writeFieldStop();" << endl << indent() << "oprot.writeStructEnd();" + << endl; + + scope_down(out, endl2); +} + +/** + * Generates a function to write all the fields of the struct, + * which is a function result. These fields are only written + * if they are set in the Isset array, and only one of them + * can be set at a time. + * + * @param tstruct The struct definition + */ +void t_dart_generator::generate_dart_struct_result_writer(ofstream& out, t_struct* tstruct) { + indent(out) << "write(TProtocol oprot)"; + scope_up(out); + + string name = tstruct->get_name(); + const vector& fields = tstruct->get_sorted_members(); + vector::const_iterator f_iter; + + indent(out) << "oprot.writeStructBegin(_STRUCT_DESC);" << endl2; + + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + indent(out) << "if "; + } else { + out << " else if "; + } + + out << "(this." << generate_isset_check(*f_iter) << ")"; + scope_up(out); + + indent(out) << "oprot.writeFieldBegin(_" << constant_name((*f_iter)->get_name()) + << "_FIELD_DESC);" << endl; + + // Write field contents + generate_serialize_field(out, *f_iter, "this."); + + // Write field closer + indent(out) << "oprot.writeFieldEnd();" << endl; + + scope_down(out, ""); + } + out << endl; + + // Write the struct map + indent(out) << "oprot.writeFieldStop();" << endl << indent() + << "oprot.writeStructEnd();" << endl; + + scope_down(out, endl2); +} + +void t_dart_generator::generate_generic_field_getters(std::ofstream& out, + t_struct* tstruct) { + // create the getter + indent(out) << "getFieldValue(int fieldID)"; + scope_up(out); + + indent(out) << "switch (fieldID)"; + scope_up(out); + + const vector& fields = tstruct->get_members(); + vector::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + std::string field_name = get_field_name(field->get_name()); + + indent(out) << "case " << upcase_string(field_name) << ":" << endl; + indent_up(); + indent(out) << "return this." << field_name << ";" << endl; + indent_down(); + } + + indent(out) << "default:" << endl; + indent_up(); + indent(out) << "throw new ArgumentError(\"Field $fieldID doesn't exist!\");" << endl; + indent_down(); + + scope_down(out); // switch + scope_down(out, endl2); // method +} + +void t_dart_generator::generate_generic_field_setters(std::ofstream& out, + t_struct* tstruct) { + + // create the setter + indent(out) << "setFieldValue(int fieldID, Object value)"; + scope_up(out); + + indent(out) << "switch (fieldID)"; + scope_up(out); + + // build up the bodies of both the getter and setter at once + const vector& fields = tstruct->get_members(); + vector::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + std::string field_name = get_field_name(field->get_name()); + + indent(out) << "case " << upcase_string(field_name) << ":" << endl; + indent_up(); + + indent(out) << "if (value == null)"; + scope_up(out); + indent(out) << "unset" << get_cap_name(field_name) << "();" << endl; + + scope_down(out, " else"); + scope_up(out); + indent(out) << "this." << field_name << " = value;" << endl; + scope_down(out); + + indent(out) << "break;" << endl; + + indent_down(); + out << endl; + } + + indent(out) << "default:" << endl; + indent_up(); + indent(out) << "throw new ArgumentError(\"Field $fieldID doesn't exist!\");" << endl; + indent_down(); + + scope_down(out); // switch + scope_down(out, endl2); // method +} + +// Creates a generic isSet method that takes the field number as argument +void t_dart_generator::generate_generic_isset_method(std::ofstream& out, t_struct* tstruct) { + const vector& fields = tstruct->get_members(); + vector::const_iterator f_iter; + + // create the isSet method + indent(out) << "// Returns true if field corresponding to fieldID is set (has been assigned a " + "value) and false otherwise" << endl; + indent(out) << "bool isSet(int fieldID)"; + scope_up(out); + + indent(out) << "switch (fieldID)"; + scope_up(out); + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + indent(out) << "case " << upcase_string(field->get_name()) << ":" << endl; + indent_up(); + indent(out) << "return " << generate_isset_check(field) << ";" << endl; + indent_down(); + } + + indent(out) << "default:" << endl; + indent_up(); + indent(out) << "throw new ArgumentError(\"Field $fieldID doesn't exist!\");" << endl; + indent_down(); + + scope_down(out); // switch + scope_down(out, endl2); // method +} + +/** + * Generates a set of Dart Bean boilerplate functions (setters, getters, etc.) + * for the given struct. + * + * @param tstruct The struct definition + */ +void t_dart_generator::generate_dart_bean_boilerplate(ofstream& out, + t_struct* tstruct) { + const vector& fields = tstruct->get_members(); + vector::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + t_type* type = get_true_type(field->get_type()); + std::string field_name = get_field_name(field->get_name()); + std::string cap_name = get_cap_name(field_name); + + indent(out) << "// " << field_name << endl; + + // Simple getter + generate_dart_doc(out, field); + indent(out) << type_name(type) << " get " << field_name << " => this._" << field_name << ";" << endl2; + + // Simple setter + generate_dart_doc(out, field); + indent(out) << "set " << field_name << "(" << type_name(type) << " " << field_name + << ") => this._" << field_name << " = " << field_name << ";" << endl2; + + // isSet method + indent(out) << "bool is" << get_cap_name("set") << cap_name << "()"; + if (type_can_be_null(type)) { + out << " => this." << field_name << " != null;" << endl2; + } else { + out << " => this.__isset_" << field_name << ";" << endl2; + } + + // Unsetter + indent(out) << "unset" << cap_name << "()"; + scope_up(out); + if (type_can_be_null(type)) { + indent(out) << "this." << field_name << " = null;" << endl; + } else { + indent(out) << "this.__isset_" << field_name << " = false;" << endl; + } + scope_down(out, endl2); + } +} + +/** + * Generates a toString() method for the given struct + * + * @param tstruct The struct definition + */ +void t_dart_generator::generate_dart_struct_tostring(ofstream& out, + t_struct* tstruct) { + indent(out) << "String toString()"; + scope_up(out); + + indent(out) << "StringBuffer ret = new StringBuffer(\"" + << tstruct->get_name() << "(\");" << endl2; + + const vector& fields = tstruct->get_members(); + vector::const_iterator f_iter; + + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool could_be_unset = (*f_iter)->get_req() == t_field::T_OPTIONAL; + if (could_be_unset) { + indent(out) << "if (" << generate_isset_check(*f_iter) << ")"; + scope_up(out); + } + + t_field* field = (*f_iter); + std::string field_name = get_field_name(field->get_name()); + + if (!first) { + indent(out) << "ret.write(\", \");" << endl; + } + indent(out) << "ret.write(\"" << field_name << ":\");" << endl; + bool can_be_null = type_can_be_null(field->get_type()); + if (can_be_null) { + indent(out) << "if (this." << field_name << " == null)"; + scope_up(out); + indent(out) << "ret.write(\"null\");" << endl; + scope_down(out, " else"); + scope_up(out); + } + + if (field->get_type()->is_base_type() && ((t_base_type*)(field->get_type()))->is_binary()) { + indent(out) << "ret.write(\"BINARY\");" << endl; + } else if (field->get_type()->is_enum()) { + indent(out) << "String " << field_name << "_name = " + << get_enum_class_name(field->get_type()) + << ".VALUES_TO_NAMES[this." << field_name << "];" << endl; + indent(out) << "if (" << field_name << "_name != null)"; + scope_up(out); + indent(out) << "ret.write(" << field_name << "_name);" << endl; + indent(out) << "ret.write(\" (\");" << endl; + scope_down(out); + indent(out) << "ret.write(this." << field_name << ");" << endl; + indent(out) << "if (" << field_name << "_name != null)"; + scope_up(out); + indent(out) << "ret.write(\")\");" << endl; + scope_down(out); + } else { + indent(out) << "ret.write(this." << field_name << ");" << endl; + } + + if (can_be_null) { + scope_down(out); + } + if (could_be_unset) { + scope_down(out); + } + + out << endl; + first = false; + } + + indent(out) << "ret.write(\")\");" << endl2; + + indent(out) << "return ret.toString();" << endl; + + scope_down(out, endl2); +} + +/** + * Returns a string with the dart representation of the given thrift type + * (e.g. for the type struct it returns "TType.STRUCT") + */ +std::string t_dart_generator::get_dart_type_string(t_type* type) { + if (type->is_list()) { + return "TType.LIST"; + } else if (type->is_map()) { + return "TType.MAP"; + } else if (type->is_set()) { + return "TType.SET"; + } else if (type->is_struct() || type->is_xception()) { + return "TType.STRUCT"; + } else if (type->is_enum()) { + return "TType.I32"; + } else if (type->is_typedef()) { + return get_dart_type_string(((t_typedef*)type)->get_type()); + } else if (type->is_base_type()) { + switch (((t_base_type*)type)->get_base()) { + case t_base_type::TYPE_VOID: + return "TType.VOID"; + break; + case t_base_type::TYPE_STRING: + return "TType.STRING"; + break; + case t_base_type::TYPE_BOOL: + return "TType.BOOL"; + break; + case t_base_type::TYPE_BYTE: + return "TType.BYTE"; + break; + case t_base_type::TYPE_I16: + return "TType.I16"; + break; + case t_base_type::TYPE_I32: + return "TType.I32"; + break; + case t_base_type::TYPE_I64: + return "TType.I64"; + break; + case t_base_type::TYPE_DOUBLE: + return "TType.DOUBLE"; + break; + default: + throw std::runtime_error("Unknown thrift type \"" + type->get_name() + + "\" passed to t_dart_generator::get_dart_type_string!"); + break; // This should never happen! + } + } else { + throw std::runtime_error( + "Unknown thrift type \"" + type->get_name() + + "\" passed to t_dart_generator::get_dart_type_string!"); // This should never happen! + } +} + +void t_dart_generator::generate_service(t_service* tservice) { + string file_name = get_file_name(service_name_); + string f_service_name = src_dir_ + "/" + file_name + ".dart"; + f_service_.open(f_service_name.c_str()); + + f_service_ << autogen_comment() << dart_library(file_name) << endl; + + f_service_ << dart_async_imports() << dart_thrift_imports() + << dart_thrift_gen_imports(tservice) << endl; + + if (tservice->get_extends() != NULL) { + t_type* parent = tservice->get_extends(); + f_service_ << "import '" << get_file_name(parent->get_name()) << ".dart';" << endl; + } + + f_service_ << endl; + + generate_service_interface(tservice); + generate_service_client(tservice); + generate_service_server(tservice); + generate_service_helpers(tservice); + + f_service_.close(); +} + +/** + * Generates a service interface definition. + * + * @param tservice The service to generate a header definition for + */ +void t_dart_generator::generate_service_interface(t_service* tservice) { + string extends_iface = ""; + if (tservice->get_extends() != NULL) { + extends_iface = " extends " + tservice->get_extends()->get_name(); + } + + generate_dart_doc(f_service_, tservice); + + string class_name = service_name_; + export_class_to_library(get_file_name(service_name_), class_name); + indent(f_service_) << "abstract class " << class_name << extends_iface; + scope_up(f_service_); + + vector functions = tservice->get_functions(); + vector::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << endl; + generate_dart_doc(f_service_, *f_iter); + indent(f_service_) << function_signature(*f_iter) << ";" << endl; + } + + scope_down(f_service_, endl2); +} + +/** + * Generates structs for all the service args and return types + * + * @param tservice The service + */ +void t_dart_generator::generate_service_helpers(t_service* tservice) { + vector functions = tservice->get_functions(); + vector::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + generate_dart_struct_definition(f_service_, ts, false, false); + generate_function_helpers(*f_iter); + } +} + +/** + * Generates a service client definition. + * + * @param tservice The service to generate a server for. + */ +void t_dart_generator::generate_service_client(t_service* tservice) { + string extends = ""; + string extends_client = ""; + if (tservice->get_extends() != NULL) { + extends = tservice->get_extends()->get_name(); + extends_client = " extends " + extends + "Impl"; + } + + string class_name = service_name_ + "Impl"; + export_class_to_library(get_file_name(service_name_), class_name); + indent(f_service_) << "class " << class_name << extends_client + << " extends " << service_name_; + scope_up(f_service_); + f_service_ << endl; + + indent(f_service_) << class_name << "(TProtocol iprot, TProtocol oprot=null)"; + scope_up(f_service_); + + if (extends.empty()) { + indent(f_service_) << "_iprot = iprot;" << endl; + indent(f_service_) << "_oprot = (oprot == null) ? iprot : oprot;" << endl; + } else { + indent(f_service_) << "super(iprot, oprot);" << endl; + } + scope_down(f_service_); + f_service_ << endl; + + if (extends.empty()) { + indent(f_service_) << "TProtocol _iprot;" << endl; + indent(f_service_) << "TProtocol _oprot;" << endl; + indent(f_service_) << "int _seqid;" << endl << endl; + + indent(f_service_) << "TProtocol getInputProtocol() => _iprot;" << endl2; + indent(f_service_) << "TProtocol getOutputProtocol() => _oprot;" << endl2; + } + + // Generate client method implementations + vector functions = tservice->get_functions(); + vector::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string funname = (*f_iter)->get_name(); + + // Open function + indent(f_service_) << function_signature(*f_iter) << " async"; + scope_up(f_service_); + + // Get the struct of function call params + t_struct* arg_struct = (*f_iter)->get_arglist(); + + string argsname = (*f_iter)->get_name() + "_args"; + vector::const_iterator fld_iter; + const vector& fields = arg_struct->get_members(); + + // Serialize the request + indent(f_service_) << "_oprot.writeMessageBegin(new TMessage(\"" << funname << "\", " + << ((*f_iter)->is_oneway() ? "TMessageType.ONEWAY" : "TMessageType.CALL") + << ", _seqid));" << endl; + indent(f_service_) << argsname << " args = new " << argsname << "();" << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + indent(f_service_) << "args." << (*fld_iter)->get_name() << " = " + << (*fld_iter)->get_name() << ";" << endl; + } + + indent(f_service_) << "args.write(_oprot);" << endl; + indent(f_service_) << "_oprot.writeMessageEnd();" << endl2; + + indent(f_service_) << "await _oprot.getTransport().flush();" << endl2; + + if (!(*f_iter)->is_oneway()) { + indent(f_service_) << "TMessage msg = _iprot.readMessageBegin();" << endl; + indent(f_service_) << "if (msg.type == TMessageType.EXCEPTION)"; + scope_up(f_service_); + indent(f_service_) << "TApplicationError error = TApplicationError.read(_iprot);" << endl; + indent(f_service_) << "_iprot.readMessageEnd();" << endl; + indent(f_service_) << "throw error;" << endl; + scope_down(f_service_, endl2); + + string result_class = (*f_iter)->get_name() + "_result"; + indent(f_service_) << result_class << " result = new " << result_class << "();" << endl; + indent(f_service_) << "result.read(_iprot);" << endl; + indent(f_service_) << "_iprot.readMessageEnd();" << endl; + + // Careful, only return _result if not a void function + if (!(*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << "if (result." << generate_isset_check("success") << ")"; + scope_up(f_service_); + indent(f_service_) << "return result.success;" << endl; + scope_down(f_service_, endl2); + } + + t_struct* xs = (*f_iter)->get_xceptions(); + const std::vector& xceptions = xs->get_members(); + vector::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + string result_field_name = get_field_name((*x_iter)->get_name()); + indent(f_service_) << "if (result." << result_field_name << " != null)"; + scope_up(f_service_); + indent(f_service_) << "throw result." << result_field_name << ";" << endl; + scope_down(f_service_); + } + + // If you get here it's an exception, unless a void function + if ((*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << "return;" << endl; + } else { + indent(f_service_) << "throw new TApplicationError(TApplicationError.MISSING_RESULT, \"" + << (*f_iter)->get_name() << " failed: unknown result\"));" << endl; + } + } + + scope_down(f_service_, endl2); + } + + scope_down(f_service_, endl2); +} + +/** + * Generates a service server definition. + * + * @param tservice The service to generate a server for. + */ +void t_dart_generator::generate_service_server(t_service* tservice) { + // Generate the dispatch methods + vector functions = tservice->get_functions(); + vector::iterator f_iter; + + // typedef + indent(f_service_) << "typedef void ProcessFunction(int seqid, TProtocol iprot, TProtocol oprot);" << endl2; + + // Extends stuff + string extends = ""; + string extends_processor = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_processor = " extends " + extends + "Processor"; + } + + // Generate the header portion + string class_name = service_name_ + "Processor"; + export_class_to_library(get_file_name(service_name_), class_name); + indent(f_service_) << "class " << class_name << extends_processor << " implements TProcessor"; + scope_up(f_service_); + + indent(f_service_) << class_name << "(" << service_name_ << " iface)"; + scope_up(f_service_); + if (!extends.empty()) { + indent(f_service_) << "super(iface);" << endl; + } + indent(f_service_) << "iface_ = iface;" << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + indent(f_service_) << "PROCESS_MAP[\"" << (*f_iter)->get_name() + << "\"] = " << (*f_iter)->get_name() << ";" << endl; + } + scope_down(f_service_, endl2); + + indent(f_service_) << service_name_ << " iface_;" << endl; + + if (extends.empty()) { + indent(f_service_) << "final Map PROCESS_MAP = {};" << endl; + } + + f_service_ << endl; + + // Generate the server implementation + indent(f_service_) << "bool process(TProtocol iprot, TProtocol oprot)"; + scope_up(f_service_); + indent(f_service_) << "TMessage msg = iprot.readMessageBegin();" << endl; + indent(f_service_) << "ProcessFunction fn = PROCESS_MAP[msg.name];" << endl; + indent(f_service_) << "if (fn == null)"; + scope_up(f_service_); + indent(f_service_) << "TProtocolUtil.skip(iprot, TType.STRUCT);" << endl; + indent(f_service_) << "iprot.readMessageEnd();" << endl; + indent(f_service_) << "TApplicationError x = new TApplicationError(TApplicationError.UNKNOWN_METHOD, " + "\"Invalid method name: '\"+msg.name+\"'\");" << endl; + indent(f_service_) << "oprot.writeMessageBegin(new TMessage(msg.name, TMessageType.EXCEPTION, msg.seqid));" << endl; + indent(f_service_) << "x.write(oprot);" << endl; + indent(f_service_) << "oprot.writeMessageEnd();" << endl; + indent(f_service_) << "oprot.getTransport().flush();" << endl; + indent(f_service_) << "return true;" << endl; + scope_down(f_service_); + indent(f_service_) << "fn(msg.seqid, iprot, oprot);" << endl; + indent(f_service_) << "return true;" << endl; + scope_down(f_service_, endl2); // process function + + // Generate the process subfunctions + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(tservice, *f_iter); + } + + scope_down(f_service_, endl2); // class +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_dart_generator::generate_function_helpers(t_function* tfunction) { + if (tfunction->is_oneway()) { + return; + } + + t_struct result(program_, tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector& fields = xs->get_members(); + vector::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + + generate_dart_struct_definition(f_service_, &result, false, true); +} + +/** + * Generates a process function definition. + * + * @param tfunction The function to write a dispatcher for + */ +void t_dart_generator::generate_process_function(t_service* tservice, t_function* tfunction) { + (void)tservice; + + bool await_result = (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()); + + indent(f_service_) << tfunction->get_name() << "(int seqid, TProtocol iprot, TProtocol oprot)"; + if (await_result) { + f_service_ << " async"; + } + scope_up(f_service_); + + string argsname = tfunction->get_name() + "_args"; + string resultname = tfunction->get_name() + "_result"; + + indent(f_service_) << argsname << " args = new " << argsname << "();" << endl; + indent(f_service_) << "args.read(iprot);" << endl; + indent(f_service_) << "iprot.readMessageEnd();" << endl; + + t_struct* xs = tfunction->get_xceptions(); + const std::vector& xceptions = xs->get_members(); + vector::const_iterator x_iter; + + if (!tfunction->is_oneway()) { + indent(f_service_) << resultname << " result = new " << resultname << "();" << endl; + } + + if (!tfunction->is_oneway() && xceptions.size() > 0) { + indent(f_service_) << "try"; + scope_up(f_service_); + } + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector& fields = arg_struct->get_members(); + vector::const_iterator f_iter; + + f_service_ << indent(); + if (await_result) { + f_service_ << "result.success = await "; + } + f_service_ << "iface_." << tfunction->get_name() << "("; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "args." << get_field_name((*f_iter)->get_name()); + } + f_service_ << ");" << endl; + + // Set isset on success field + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void() + && !type_can_be_null(tfunction->get_returntype())) { + indent(f_service_) << "result.set" << get_cap_name("success") << get_cap_name("isSet") + << "(true);" << endl; + } + + if (!tfunction->is_oneway() && xceptions.size() > 0) { + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + string result_field_name = get_field_name((*x_iter)->get_name()); + scope_down(f_service_, ""); + f_service_ << " on " << type_name((*x_iter)->get_type(), false, false) + << " catch(" << result_field_name << ")"; + scope_up(f_service_); + if (!tfunction->is_oneway()) { + indent(f_service_) << "result." << result_field_name << " = " + << result_field_name << ";" << endl; + } + } + scope_down(f_service_, " "); + f_service_ << "catch (th)"; + scope_up(f_service_); + indent(f_service_) << "// Internal error" << endl; + indent(f_service_) << "TApplicationError x = new " + "TApplicationError(TApplicationError.INTERNAL_ERROR, \"Internal error processing " + << tfunction->get_name() << "\");" << endl; + indent(f_service_) << "oprot.writeMessageBegin(new TMessage(\"" << tfunction->get_name() + << "\", TMessageType.EXCEPTION, seqid));" << endl; + indent(f_service_) << "x.write(oprot);" << endl; + indent(f_service_) << "oprot.writeMessageEnd();" << endl; + indent(f_service_) << "oprot.getTransport().flush();" << endl; + indent(f_service_) << "return;" << endl; + scope_down(f_service_); + } + + if (tfunction->is_oneway()) { + indent(f_service_) << "return;" << endl; + } else { + indent(f_service_) << "oprot.writeMessageBegin(new TMessage(\"" << tfunction->get_name() + << "\", TMessageType.REPLY, seqid));" << endl; + indent(f_service_) << "result.write(oprot);" << endl; + indent(f_service_) << "oprot.writeMessageEnd();" << endl; + indent(f_service_) << "oprot.getTransport().flush();" << endl; + } + + scope_down(f_service_, endl2); +} + +/** + * Deserializes a field of any type. + * + * @param tfield The field + * @param prefix The variable name or container for this field + */ +void t_dart_generator::generate_deserialize_field(ofstream& out, t_field* tfield, string prefix) { + t_type* type = get_true_type(tfield->get_type()); + string field_name = get_field_name(tfield->get_name()); + + if (type->is_void()) { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + field_name; + } + + string name = prefix + field_name; + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type, name); + } else if (type->is_container()) { + generate_deserialize_container(out, type, name); + } else if (type->is_base_type() || type->is_enum()) { + + indent(out) << name << " = iprot."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (((t_base_type*)type)->is_binary()) { + out << "readBinary();"; + } else { + out << "readString();"; + } + break; + case t_base_type::TYPE_BOOL: + out << "readBool();"; + break; + case t_base_type::TYPE_BYTE: + out << "readByte();"; + break; + case t_base_type::TYPE_I16: + out << "readI16();"; + break; + case t_base_type::TYPE_I32: + out << "readI32();"; + break; + case t_base_type::TYPE_I64: + out << "readI64();"; + break; + case t_base_type::TYPE_DOUBLE: + out << "readDouble();"; + break; + default: + throw "compiler error: no Dart name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "readI32();"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", + field_name.c_str(), + type_name(type).c_str()); + } +} + +/** + * Generates an unserializer for a struct, invokes read() + */ +void t_dart_generator::generate_deserialize_struct(ofstream& out, t_struct* tstruct, string prefix) { + indent(out) << prefix << " = new " << type_name(tstruct) << "();" << endl; + indent(out) << prefix << ".read(iprot);" << endl; +} + +/** + * Deserializes a container by reading its size and then iterating + */ +void t_dart_generator::generate_deserialize_container(ofstream& out, t_type* ttype, string prefix) { + indent(out); + scope_up(out, ""); + + string obj; + + if (ttype->is_map()) { + obj = tmp("_map"); + } else if (ttype->is_set()) { + obj = tmp("_set"); + } else if (ttype->is_list()) { + obj = tmp("_list"); + } + + // Declare variables, read header + if (ttype->is_map()) { + indent(out) << "TMap " << obj << " = iprot.readMapBegin();" << endl; + } else if (ttype->is_set()) { + indent(out) << "TSet " << obj << " = iprot.readSetBegin();" << endl; + } else if (ttype->is_list()) { + indent(out) << "TList " << obj << " = iprot.readListBegin();" << endl; + } + + indent(out) << prefix << " = new " << type_name(ttype, false, true) << "();" << endl; + + // For loop iterates over elements + string i = tmp("_i"); + indent(out) << "for (int " << i << " = 0; " << i << " < " << obj << ".length" + << "; " + << "++" << i << ")"; + scope_up(out); + + if (ttype->is_map()) { + generate_deserialize_map_element(out, (t_map*)ttype, prefix); + } else if (ttype->is_set()) { + generate_deserialize_set_element(out, (t_set*)ttype, prefix); + } else if (ttype->is_list()) { + generate_deserialize_list_element(out, (t_list*)ttype, prefix); + } + + scope_down(out); + + // Read container end + if (ttype->is_map()) { + indent(out) << "iprot.readMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "iprot.readSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "iprot.readListEnd();" << endl; + } + + scope_down(out); +} + +/** + * Generates code to deserialize a map + */ +void t_dart_generator::generate_deserialize_map_element(ofstream& out, t_map* tmap, string prefix) { + string key = tmp("_key"); + string val = tmp("_val"); + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + indent(out) << declare_field(&fkey) << endl; + indent(out) << declare_field(&fval) << endl; + + generate_deserialize_field(out, &fkey); + generate_deserialize_field(out, &fval); + + indent(out) << prefix << "[" << key << "] = " << val << ";" << endl; +} + +/** + * Deserializes a set element + */ +void t_dart_generator::generate_deserialize_set_element(ofstream& out, t_set* tset, string prefix) { + string elem = tmp("_elem"); + t_field felem(tset->get_elem_type(), elem); + + indent(out) << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".add(" << elem << ");" << endl; +} + +/** + * Deserializes a list element + */ +void t_dart_generator::generate_deserialize_list_element(ofstream& out, + t_list* tlist, + string prefix) { + string elem = tmp("_elem"); + t_field felem(tlist->get_elem_type(), elem); + + indent(out) << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".add(" << elem << ");" << endl; +} + +/** + * Serializes a field of any type. + * + * @param tfield The field to serialize + * @param prefix Name to prepend to field name + */ +void t_dart_generator::generate_serialize_field(ofstream& out, t_field* tfield, string prefix) { + t_type* type = get_true_type(tfield->get_type()); + string field_name = get_field_name(tfield->get_name()); + + // Do nothing for void types + if (type->is_void()) { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + prefix + field_name; + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, prefix + field_name); + } else if (type->is_container()) { + generate_serialize_container(out, type, prefix + field_name); + } else if (type->is_base_type() || type->is_enum()) { + + string name = prefix + field_name; + indent(out) << "oprot."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (((t_base_type*)type)->is_binary()) { + out << "writeBinary(" << name << ");"; + } else { + out << "writeString(" << name << ");"; + } + break; + case t_base_type::TYPE_BOOL: + out << "writeBool(" << name << ");"; + break; + case t_base_type::TYPE_BYTE: + out << "writeByte(" << name << ");"; + break; + case t_base_type::TYPE_I16: + out << "writeI16(" << name << ");"; + break; + case t_base_type::TYPE_I32: + out << "writeI32(" << name << ");"; + break; + case t_base_type::TYPE_I64: + out << "writeI64(" << name << ");"; + break; + case t_base_type::TYPE_DOUBLE: + out << "writeDouble(" << name << ");"; + break; + default: + throw "compiler error: no Dart name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "writeI32(" << name << ");"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s%s' TYPE '%s'\n", + prefix.c_str(), + field_name.c_str(), + type_name(type).c_str()); + } +} + +/** + * Serializes all the members of a struct. + * + * @param tstruct The struct to serialize + * @param prefix String prefix to attach to all fields + */ +void t_dart_generator::generate_serialize_struct(ofstream& out, t_struct* tstruct, string prefix) { + (void)tstruct; + indent(out) << prefix << ".write(oprot);" << endl; +} + +/** + * Serializes a container by writing its size then the elements. + * + * @param ttype The type of container + * @param prefix String prefix for fields + */ +void t_dart_generator::generate_serialize_container(ofstream& out, t_type* ttype, string prefix) { + indent(out); + scope_up(out, ""); + + if (ttype->is_map()) { + string iter = tmp("_key"); + string counter = tmp("_sizeCounter"); + indent(out) << "int " << counter << " = 0;" << endl; + indent(out) << "for (var " << iter << " in " << prefix << ")"; + scope_up(out); + indent(out) << counter << +"++;" << endl; + scope_down(out); + + indent(out) << "oprot.writeMapBegin(new TMap(" << type_to_enum(((t_map*)ttype)->get_key_type()) + << ", " << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " << counter << "));" + << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.writeSetBegin(new TSet(" << type_to_enum(((t_set*)ttype)->get_elem_type()) + << ", " << prefix << ".length));" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.writeListBegin(new TList(" + << type_to_enum(((t_list*)ttype)->get_elem_type()) << ", " << prefix << ".length));" + << endl; + } + + string iter = tmp("elem"); + if (ttype->is_map() || ttype->is_set() || ttype->is_list()) { + indent(out) << "for (var " << iter << " in " << prefix << ")"; + } + + scope_up(out); + + if (ttype->is_map()) { + generate_serialize_map_element(out, (t_map*)ttype, iter, prefix); + } else if (ttype->is_set()) { + generate_serialize_set_element(out, (t_set*)ttype, iter); + } else if (ttype->is_list()) { + generate_serialize_list_element(out, (t_list*)ttype, iter); + } + + scope_down(out); + + if (ttype->is_map()) { + indent(out) << "oprot.writeMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.writeSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.writeListEnd();" << endl; + } + + scope_down(out); +} + +/** + * Serializes the members of a map. + */ +void t_dart_generator::generate_serialize_map_element(ofstream& out, + t_map* tmap, + string iter, + string map) { + t_field kfield(tmap->get_key_type(), iter); + generate_serialize_field(out, &kfield, ""); + t_field vfield(tmap->get_val_type(), map + "[" + iter + "]"); + generate_serialize_field(out, &vfield, ""); +} + +/** + * Serializes the members of a set. + */ +void t_dart_generator::generate_serialize_set_element(ofstream& out, t_set* tset, string iter) { + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +/** + * Serializes the members of a list. + */ +void t_dart_generator::generate_serialize_list_element(ofstream& out, t_list* tlist, string iter) { + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +/** + * Returns a Dart type name + * + * @param ttype The type + * @param container Is the type going inside a container? + * @return Dart type name, i.e. HashMap + */ +string t_dart_generator::type_name(t_type* ttype, bool in_container, bool in_init) { + (void)in_init; + ttype = get_true_type(ttype); + string prefix; + + if (ttype->is_base_type()) { + return base_type_name((t_base_type*)ttype, in_container); + } else if (ttype->is_enum()) { + return "int"; + } else if (ttype->is_map()) { + return "Map"; + } else if (ttype->is_set()) { + return "Set"; + } else if (ttype->is_list()) { + return "List"; + } + + return ttype->get_name(); +} + +/** + * Returns the Dart type that corresponds to the thrift type. + * + * @param tbase The base type + * @param container Is it going in a Dart container? + */ +string t_dart_generator::base_type_name(t_base_type* type, bool in_container) { + (void)in_container; + t_base_type::t_base tbase = type->get_base(); + + switch (tbase) { + case t_base_type::TYPE_VOID: + return "void"; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + return "ByteBuffer"; + } else { + return "String"; + } + case t_base_type::TYPE_BOOL: + return "Boolean"; + case t_base_type::TYPE_BYTE: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + return "int"; + case t_base_type::TYPE_DOUBLE: + return "double"; + default: + throw "compiler error: no Dart name for base type " + t_base_type::t_base_name(tbase); + } +} + +/** + * Declares a field, which may include initialization as necessary. + * + * @param ttype The type + */ +string t_dart_generator::declare_field(t_field* tfield, bool init) { + string field_name = get_field_name(tfield->get_name()); + string result = type_name(tfield->get_type()) + " " + field_name; + if (init) { + t_type* ttype = get_true_type(tfield->get_type()); + if (ttype->is_base_type() && tfield->get_value() != NULL) { + ofstream dummy; + result += " = " + render_const_value(dummy, field_name, ttype, tfield->get_value()); + } else if (ttype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)ttype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + result += " = null"; + break; + case t_base_type::TYPE_BOOL: + result += " = false"; + break; + case t_base_type::TYPE_BYTE: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + result += " = 0"; + break; + case t_base_type::TYPE_DOUBLE: + result += " = 0.0"; + break; + } + + } else if (ttype->is_enum()) { + result += " = 0"; + } else if (ttype->is_container()) { + result += " = new " + type_name(ttype, false, true) + "()"; + } else { + result += " = new " + type_name(ttype, false, true) + "()"; + ; + } + } + return result + ";"; +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_dart_generator::function_signature(t_function* tfunction) { + std::string arguments = argument_list(tfunction->get_arglist()); + + std::string returntype; + if (tfunction->get_returntype()->is_void()) { + returntype = "Future"; + } else { + returntype = "Future<" + type_name(tfunction->get_returntype()) + ">"; + } + + std::string result = returntype + " " + tfunction->get_name() + "(" + arguments + ")"; + return result; +} + +/** + * Renders a comma separated field list, with type names + */ +string t_dart_generator::argument_list(t_struct* tstruct) { + string result = ""; + + const vector& fields = tstruct->get_members(); + vector::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + string field_name = get_field_name((*f_iter)->get_name()); + result += type_name((*f_iter)->get_type()) + " " + field_name; + } + return result; +} + +/** + * Converts the parse type to a C++ enum string for the given type. + */ +string t_dart_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "TType.STRING"; + case t_base_type::TYPE_BOOL: + return "TType.BOOL"; + case t_base_type::TYPE_BYTE: + return "TType.BYTE"; + case t_base_type::TYPE_I16: + return "TType.I16"; + case t_base_type::TYPE_I32: + return "TType.I32"; + case t_base_type::TYPE_I64: + return "TType.I64"; + case t_base_type::TYPE_DOUBLE: + return "TType.DOUBLE"; + } + } else if (type->is_enum()) { + return "TType.I32"; + } else if (type->is_struct() || type->is_xception()) { + return "TType.STRUCT"; + } else if (type->is_map()) { + return "TType.MAP"; + } else if (type->is_set()) { + return "TType.SET"; + } else if (type->is_list()) { + return "TType.LIST"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +std::string t_dart_generator::get_cap_name(std::string name) { + name[0] = toupper(name[0]); + return name; +} + +std::string t_dart_generator::get_field_name(std::string name) { + name[0] = tolower(name[0]); + return name; +} + +std::string t_dart_generator::get_file_name(std::string name) { + // e.g. change APIForFileIO to api_for_file_io + + string ret; + const char* tmp = name.c_str(); + bool is_prev_lc = true; + bool is_current_lc = tmp[0] == tolower(tmp[0]); + bool is_next_lc = false; + + for (unsigned int i = 0; i < name.length(); i++) { + char lc = tolower(tmp[i]); + + if (i == name.length() - 1) { + is_next_lc = false; + } else { + is_next_lc = (tmp[i+1] == tolower(tmp[i+1])); + } + + if (i != 0 && !is_current_lc && (is_prev_lc || is_next_lc)) { + ret += "_"; + } + ret += lc; + + is_prev_lc = is_current_lc; + is_current_lc = is_next_lc; + } + + return ret; +} + +string t_dart_generator::constant_name(string name) { + string constant_name; + + bool is_first = true; + bool was_previous_char_upper = false; + for (string::iterator iter = name.begin(); iter != name.end(); ++iter) { + string::value_type character = (*iter); + + bool is_upper = isupper(character); + + if (is_upper && !is_first && !was_previous_char_upper) { + constant_name += '_'; + } + constant_name += toupper(character); + + is_first = false; + was_previous_char_upper = is_upper; + } + + return constant_name; +} + +/** + * Emits a doc comment if the provided object has a doc in Thrift + */ +void t_dart_generator::generate_dart_doc(ofstream& out, t_doc* tdoc) { + if (tdoc->has_doc()) { + generate_docstring_comment(out, "///\n", "///", tdoc->get_doc(), "///\n"); + } +} + +/** + * Emits a doc comment if the provided function object has a doc in Thrift + */ +void t_dart_generator::generate_dart_doc(ofstream& out, t_function* tfunction) { + if (tfunction->has_doc()) { + stringstream ss; + ss << tfunction->get_doc(); + const vector& fields = tfunction->get_arglist()->get_members(); + vector::const_iterator p_iter; + for (p_iter = fields.begin(); p_iter != fields.end(); ++p_iter) { + t_field* p = *p_iter; + string field_name = get_field_name(p->get_name()); + ss << "\n@param " << field_name; + if (p->has_doc()) { + ss << " " << p->get_doc(); + } + } + generate_docstring_comment(out, "///\n", "///", ss.str(), "///\n"); + } +} + +std::string t_dart_generator::generate_isset_check(t_field* field) { + string field_name = get_field_name(field->get_name()); + return generate_isset_check(field_name); +} + +std::string t_dart_generator::generate_isset_check(std::string field_name) { + return "is" + get_cap_name("set") + get_cap_name(field_name) + "()"; +} + +void t_dart_generator::generate_isset_set(ofstream& out, t_field* field) { + if (!type_can_be_null(field->get_type())) { + string field_name = get_field_name(field->get_name()); + indent(out) << "this.__isset_" << field_name << " = true;" << endl; + } +} + +std::string t_dart_generator::get_enum_class_name(t_type* type) { + return library_name_ + "." + type->get_name(); +} + +THRIFT_REGISTER_GENERATOR( + dart, + "Dart", + " library_name=thrift Optional override for library name.\n"); From 11473fd2c44692d0fd782dca9d3fa602534eb0ac Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Sat, 29 Aug 2015 20:08:18 -0500 Subject: [PATCH 02/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Cleanup some issues in the generator found while analyzing generated code. --- compiler/cpp/src/generate/t_dart_generator.cc | 130 +++++------------- 1 file changed, 38 insertions(+), 92 deletions(-) diff --git a/compiler/cpp/src/generate/t_dart_generator.cc b/compiler/cpp/src/generate/t_dart_generator.cc index ad1a01b4458..b55636eceba 100644 --- a/compiler/cpp/src/generate/t_dart_generator.cc +++ b/compiler/cpp/src/generate/t_dart_generator.cc @@ -207,10 +207,8 @@ class t_dart_generator : public t_oop_generator { */ std::string dart_library(string file_name); - std::string dart_async_imports(); + std::string service_imports(); std::string dart_thrift_imports(); - std::string dart_thrift_gen_imports(t_struct* tstruct, string& imports); - std::string dart_thrift_gen_imports(t_service* tservice); std::string type_name(t_type* ttype, bool in_container = false, bool in_init = false); std::string base_type_name(t_base_type* tbase, bool in_container = false); std::string declare_field(t_field* tfield, bool init = false); @@ -290,7 +288,7 @@ string t_dart_generator::dart_library(string file_name) { * * @return List of imports for Dart types that are used in here */ -string t_dart_generator::dart_async_imports() { +string t_dart_generator::service_imports() { return "import 'dart:async';" + endl; } @@ -301,63 +299,11 @@ string t_dart_generator::dart_async_imports() { */ string t_dart_generator::dart_thrift_imports() { return string() + + "import 'dart:typed_data' show ByteBuffer;" + endl + "import 'package:thrift/thrift.dart';" + endl + "import 'package:" + library_name_ + "/" + library_name_ + ".dart';" + endl; } -/** - * Prints imports needed for a given type - * - * @return List of imports necessary for a given t_struct - */ -string t_dart_generator::dart_thrift_gen_imports(t_struct* tstruct, string& imports) { - - const vector& members = tstruct->get_members(); - vector::const_iterator m_iter; - - // For each type check if it is from a differnet namespace - for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { - t_program* program = (*m_iter)->get_type()->get_program(); - if (program != NULL && program != program_) { - if (!library_name_.empty()) { - if (imports.find(library_name_ + "." + (*m_iter)->get_type()->get_name()) == string::npos) { - imports.append("import " + library_name_ + "." + (*m_iter)->get_type()->get_name() + ";\n"); - } - } - } - } - return imports; -} - -/** - * Prints imports needed for a given type - * - * @return List of imports necessary for a given t_service - */ -string t_dart_generator::dart_thrift_gen_imports(t_service* tservice) { - string imports; - const vector& functions = tservice->get_functions(); - vector::const_iterator f_iter; - - // For each type check if it is from a differnet namespace - for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { - t_program* program = (*f_iter)->get_returntype()->get_program(); - if (program != NULL && program != program_) { - if (!library_name_.empty()) { - if (imports.find(library_name_ + "." + (*f_iter)->get_returntype()->get_name()) == string::npos) { - imports.append("import '" + library_name_ + "." + (*f_iter)->get_returntype()->get_name() - + "';\n"); - } - } - } - - dart_thrift_gen_imports((*f_iter)->get_arglist(), imports); - dart_thrift_gen_imports((*f_iter)->get_xceptions(), imports); - } - - return imports; -} - /** * Not used */ @@ -422,7 +368,7 @@ void t_dart_generator::generate_enum(t_enum* tenum) { // Create a static Set with all valid values for this enum f_enum << endl; - indent(f_enum) << "static final VALID_VALUES:Set = new Set([" << endl; + indent(f_enum) << "static final Set VALID_VALUES = new Set.from([" << endl; indent_up(); bool firstValue = true; for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { @@ -434,7 +380,7 @@ void t_dart_generator::generate_enum(t_enum* tenum) { indent_down(); indent(f_enum) << "]);" << endl; - indent(f_enum) << "static const VALUES_TO_NAMES:Map = new Map({" << endl; + indent(f_enum) << "static final Map VALUES_TO_NAMES = {" << endl; indent_up(); firstValue = true; for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { @@ -443,7 +389,7 @@ void t_dart_generator::generate_enum(t_enum* tenum) { firstValue = false; } indent_down(); - indent(f_enum) << "});" << endl; + indent(f_enum) << "};" << endl; scope_down(f_enum); // end class @@ -479,6 +425,7 @@ void t_dart_generator::generate_consts(std::vector consts) { (*c_iter)->get_type(), (*c_iter)->get_value(), false); + f_consts << endl; } scope_down(f_consts); @@ -568,7 +515,6 @@ void t_dart_generator::print_const_value(std::ofstream& out, indent(out) << name << "." << (type->is_list() ? "push" : "add") << "(" << val << ");" << endl; } - out << endl; } else { throw "compiler error: no const of type " + type->get_name(); } @@ -612,6 +558,7 @@ string t_dart_generator::render_const_value(ofstream& out, } else { string t = tmp("tmp"); print_const_value(out, t, type, value, true); + out << endl; render << t; } @@ -652,7 +599,7 @@ void t_dart_generator::generate_dart_struct(t_struct* tstruct, bool is_exception string imports; - f_struct << dart_thrift_imports() << dart_thrift_gen_imports(tstruct, imports) << endl; + f_struct << dart_thrift_imports() << endl; generate_dart_struct_definition(f_struct, tstruct, is_exception, false, file_name); @@ -731,9 +678,14 @@ void t_dart_generator::generate_dart_struct_definition(ofstream& out, indent(out) << tstruct->get_name() << "()"; scope_up(out); for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); if ((*m_iter)->get_value() != NULL) { - indent(out) << "this._" << get_field_name((*m_iter)->get_name()) << " = " - << (*m_iter)->get_value()->get_integer() << ";" << endl; + print_const_value(out, + "this." + (*m_iter)->get_name(), + t, + (*m_iter)->get_value(), + true, + true); } } scope_down(out); @@ -834,7 +786,7 @@ void t_dart_generator::generate_dart_struct_reader(ofstream& out, t_struct* tstr string field_name = get_field_name((*f_iter)->get_name()); indent(out) << "if (!__isset_" << field_name << ")"; scope_up(out); - indent(out) << " throw new TProtocolError(TProtocolError.UNKNOWN, \"Required field '" + indent(out) << " throw new TProtocolError(TProtocolErrorType.UNKNOWN, \"Required field '" << field_name << "' was not found in serialized data! Struct: \" + toString());" << endl; scope_down(out, endl2); @@ -863,7 +815,7 @@ void t_dart_generator::generate_dart_validator(ofstream& out, t_struct* tstruct) if (type_can_be_null((*f_iter)->get_type())) { indent(out) << "if (" << field_name << " == null)"; scope_up(out); - indent(out) << "throw new TProtocolError(TProtocolError.UNKNOWN, \"Required field '" + indent(out) << "throw new TProtocolError(TProtocolErrorType.UNKNOWN, \"Required field '" << field_name << "' was not present! Struct: \" + toString());" << endl; scope_down(out); @@ -885,9 +837,9 @@ void t_dart_generator::generate_dart_validator(ofstream& out, t_struct* tstruct) indent(out) << "if (" << generate_isset_check(field) << " && !" << get_enum_class_name(type) << ".VALID_VALUES.contains(" << field_name << "))"; scope_up(out); - indent(out) << "throw new TProtocolError(TProtocolError.UNKNOWN, \"The field '" - << field_name << "' has been assigned the invalid value \" + " - << field_name << ");" << endl; + indent(out) << "throw new TProtocolError(TProtocolErrorType.UNKNOWN, \"The field '" + << field_name << "' has been assigned the invalid value " + << "$" << field_name << "\");" << endl; scope_down(out); } } @@ -1120,8 +1072,11 @@ void t_dart_generator::generate_dart_bean_boilerplate(ofstream& out, // Simple setter generate_dart_doc(out, field); - indent(out) << "set " << field_name << "(" << type_name(type) << " " << field_name - << ") => this._" << field_name << " = " << field_name << ";" << endl2; + indent(out) << "set " << field_name << "(" << type_name(type) << " " << field_name << ")"; + scope_up(out); + indent(out) << "this._" << field_name << " = " << field_name << ";" << endl; + generate_isset_set(out, field); + scope_down(out, endl2); // isSet method indent(out) << "bool is" << get_cap_name("set") << cap_name << "()"; @@ -1282,9 +1237,7 @@ void t_dart_generator::generate_service(t_service* tservice) { f_service_.open(f_service_name.c_str()); f_service_ << autogen_comment() << dart_library(file_name) << endl; - - f_service_ << dart_async_imports() << dart_thrift_imports() - << dart_thrift_gen_imports(tservice) << endl; + f_service_ << service_imports() << dart_thrift_imports() << endl; if (tservice->get_extends() != NULL) { t_type* parent = tservice->get_extends(); @@ -1365,7 +1318,7 @@ void t_dart_generator::generate_service_client(t_service* tservice) { scope_up(f_service_); f_service_ << endl; - indent(f_service_) << class_name << "(TProtocol iprot, TProtocol oprot=null)"; + indent(f_service_) << class_name << "(TProtocol iprot, [TProtocol oprot = null])"; scope_up(f_service_); if (extends.empty()) { @@ -1417,7 +1370,7 @@ void t_dart_generator::generate_service_client(t_service* tservice) { indent(f_service_) << "args.write(_oprot);" << endl; indent(f_service_) << "_oprot.writeMessageEnd();" << endl2; - indent(f_service_) << "await _oprot.getTransport().flush();" << endl2; + indent(f_service_) << "await _oprot.transport.flush();" << endl2; if (!(*f_iter)->is_oneway()) { indent(f_service_) << "TMessage msg = _iprot.readMessageBegin();" << endl; @@ -1456,8 +1409,8 @@ void t_dart_generator::generate_service_client(t_service* tservice) { if ((*f_iter)->get_returntype()->is_void()) { indent(f_service_) << "return;" << endl; } else { - indent(f_service_) << "throw new TApplicationError(TApplicationError.MISSING_RESULT, \"" - << (*f_iter)->get_name() << " failed: unknown result\"));" << endl; + indent(f_service_) << "throw new TApplicationError(TApplicationErrorType.MISSING_RESULT, \"" + << (*f_iter)->get_name() << " failed: unknown result\");" << endl; } } @@ -1524,12 +1477,12 @@ void t_dart_generator::generate_service_server(t_service* tservice) { scope_up(f_service_); indent(f_service_) << "TProtocolUtil.skip(iprot, TType.STRUCT);" << endl; indent(f_service_) << "iprot.readMessageEnd();" << endl; - indent(f_service_) << "TApplicationError x = new TApplicationError(TApplicationError.UNKNOWN_METHOD, " + indent(f_service_) << "TApplicationError x = new TApplicationError(TApplicationErrorType.UNKNOWN_METHOD, " "\"Invalid method name: '\"+msg.name+\"'\");" << endl; indent(f_service_) << "oprot.writeMessageBegin(new TMessage(msg.name, TMessageType.EXCEPTION, msg.seqid));" << endl; indent(f_service_) << "x.write(oprot);" << endl; indent(f_service_) << "oprot.writeMessageEnd();" << endl; - indent(f_service_) << "oprot.getTransport().flush();" << endl; + indent(f_service_) << "oprot.transport.flush();" << endl; indent(f_service_) << "return true;" << endl; scope_down(f_service_); indent(f_service_) << "fn(msg.seqid, iprot, oprot);" << endl; @@ -1627,13 +1580,6 @@ void t_dart_generator::generate_process_function(t_service* tservice, t_function } f_service_ << ");" << endl; - // Set isset on success field - if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void() - && !type_can_be_null(tfunction->get_returntype())) { - indent(f_service_) << "result.set" << get_cap_name("success") << get_cap_name("isSet") - << "(true);" << endl; - } - if (!tfunction->is_oneway() && xceptions.size() > 0) { for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { string result_field_name = get_field_name((*x_iter)->get_name()); @@ -1651,13 +1597,13 @@ void t_dart_generator::generate_process_function(t_service* tservice, t_function scope_up(f_service_); indent(f_service_) << "// Internal error" << endl; indent(f_service_) << "TApplicationError x = new " - "TApplicationError(TApplicationError.INTERNAL_ERROR, \"Internal error processing " + "TApplicationError(TApplicationErrorType.INTERNAL_ERROR, \"Internal error processing " << tfunction->get_name() << "\");" << endl; indent(f_service_) << "oprot.writeMessageBegin(new TMessage(\"" << tfunction->get_name() << "\", TMessageType.EXCEPTION, seqid));" << endl; indent(f_service_) << "x.write(oprot);" << endl; indent(f_service_) << "oprot.writeMessageEnd();" << endl; - indent(f_service_) << "oprot.getTransport().flush();" << endl; + indent(f_service_) << "oprot.transport.flush();" << endl; indent(f_service_) << "return;" << endl; scope_down(f_service_); } @@ -1669,7 +1615,7 @@ void t_dart_generator::generate_process_function(t_service* tservice, t_function << "\", TMessageType.REPLY, seqid));" << endl; indent(f_service_) << "result.write(oprot);" << endl; indent(f_service_) << "oprot.writeMessageEnd();" << endl; - indent(f_service_) << "oprot.getTransport().flush();" << endl; + indent(f_service_) << "oprot.transport.flush();" << endl; } scope_down(f_service_, endl2); @@ -2073,7 +2019,7 @@ string t_dart_generator::base_type_name(t_base_type* type, bool in_container) { return "String"; } case t_base_type::TYPE_BOOL: - return "Boolean"; + return "bool"; case t_base_type::TYPE_BYTE: case t_base_type::TYPE_I16: case t_base_type::TYPE_I32: @@ -2324,7 +2270,7 @@ void t_dart_generator::generate_isset_set(ofstream& out, t_field* field) { } std::string t_dart_generator::get_enum_class_name(t_type* type) { - return library_name_ + "." + type->get_name(); + return type->get_name(); } THRIFT_REGISTER_GENERATOR( From 221f6042369b39998567909759d6350c8914a97d Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Sat, 29 Aug 2015 20:23:28 -0500 Subject: [PATCH 03/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Initial port of library code, except for transport implementations. --- lib/Makefile.am | 1 + lib/dart/.gitignore | 3 + lib/dart/README.md | 26 +++++ lib/dart/coding_standards.md | 6 + lib/dart/lib/src/protocol/t_field.dart | 28 +++++ lib/dart/lib/src/protocol/t_list.dart | 28 +++++ lib/dart/lib/src/protocol/t_map.dart | 29 +++++ lib/dart/lib/src/protocol/t_message.dart | 39 +++++++ lib/dart/lib/src/protocol/t_protocol.dart | 97 +++++++++++++++ .../lib/src/protocol/t_protocol_error.dart | 37 ++++++ .../lib/src/protocol/t_protocol_util.dart | 110 ++++++++++++++++++ lib/dart/lib/src/protocol/t_set.dart | 28 +++++ lib/dart/lib/src/protocol/t_struct.dart | 27 +++++ lib/dart/lib/src/protocol/t_type.dart | 36 ++++++ lib/dart/lib/src/t_application_error.dart | 107 +++++++++++++++++ lib/dart/lib/src/t_base.dart | 40 +++++++ lib/dart/lib/src/t_error.dart | 29 +++++ lib/dart/lib/src/t_processor.dart | 24 ++++ lib/dart/lib/src/transport/t_transport.dart | 78 +++++++++++++ .../lib/src/transport/t_transport_error.dart | 36 ++++++ lib/dart/lib/thrift.dart | 23 ++++ lib/dart/pubspec.yaml | 12 ++ 22 files changed, 844 insertions(+) create mode 100644 lib/dart/.gitignore create mode 100644 lib/dart/README.md create mode 100644 lib/dart/coding_standards.md create mode 100644 lib/dart/lib/src/protocol/t_field.dart create mode 100644 lib/dart/lib/src/protocol/t_list.dart create mode 100644 lib/dart/lib/src/protocol/t_map.dart create mode 100644 lib/dart/lib/src/protocol/t_message.dart create mode 100644 lib/dart/lib/src/protocol/t_protocol.dart create mode 100644 lib/dart/lib/src/protocol/t_protocol_error.dart create mode 100644 lib/dart/lib/src/protocol/t_protocol_util.dart create mode 100644 lib/dart/lib/src/protocol/t_set.dart create mode 100644 lib/dart/lib/src/protocol/t_struct.dart create mode 100644 lib/dart/lib/src/protocol/t_type.dart create mode 100644 lib/dart/lib/src/t_application_error.dart create mode 100644 lib/dart/lib/src/t_base.dart create mode 100644 lib/dart/lib/src/t_error.dart create mode 100644 lib/dart/lib/src/t_processor.dart create mode 100644 lib/dart/lib/src/transport/t_transport.dart create mode 100644 lib/dart/lib/src/transport/t_transport_error.dart create mode 100644 lib/dart/lib/thrift.dart create mode 100644 lib/dart/pubspec.yaml diff --git a/lib/Makefile.am b/lib/Makefile.am index 5066a00283f..3f7ddc9a891 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -88,6 +88,7 @@ EXTRA_DIST = \ as3 \ cocoa \ d \ + dart \ delphi \ haxe \ javame \ diff --git a/lib/dart/.gitignore b/lib/dart/.gitignore new file mode 100644 index 00000000000..6854399b721 --- /dev/null +++ b/lib/dart/.gitignore @@ -0,0 +1,3 @@ +packages +.pub/ +pubspec.lock diff --git a/lib/dart/README.md b/lib/dart/README.md new file mode 100644 index 00000000000..44ae70257fc --- /dev/null +++ b/lib/dart/README.md @@ -0,0 +1,26 @@ +Thrift Dart Library + +License +======= + +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. + +Using Thrift with Dart +==================== + +Dart 1.11.3 or newer is required diff --git a/lib/dart/coding_standards.md b/lib/dart/coding_standards.md new file mode 100644 index 00000000000..62f600365fe --- /dev/null +++ b/lib/dart/coding_standards.md @@ -0,0 +1,6 @@ +# Dart Coding Standards + +### Please follow: + * [Thrift General Coding Standards](/doc/coding_standards.md) + * [Use dartfmt](https://www.dartlang.org/tools/dartfmt/) and follow the + [Dart Style Guide](https://www.dartlang.org/articles/style-guide/) diff --git a/lib/dart/lib/src/protocol/t_field.dart b/lib/dart/lib/src/protocol/t_field.dart new file mode 100644 index 00000000000..bbf323ac4ac --- /dev/null +++ b/lib/dart/lib/src/protocol/t_field.dart @@ -0,0 +1,28 @@ +/// 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. + +part of thrift; + +class TField { + + final String name; + final int type; + final int id; + + TField(this.name, this.type, this.id); + +} diff --git a/lib/dart/lib/src/protocol/t_list.dart b/lib/dart/lib/src/protocol/t_list.dart new file mode 100644 index 00000000000..10429bcec70 --- /dev/null +++ b/lib/dart/lib/src/protocol/t_list.dart @@ -0,0 +1,28 @@ +/// 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. + +part of thrift; + + +class TList { + + final int elementType; + final int length; + + TList(this.elementType, this.length); + +} diff --git a/lib/dart/lib/src/protocol/t_map.dart b/lib/dart/lib/src/protocol/t_map.dart new file mode 100644 index 00000000000..e448ccaaad2 --- /dev/null +++ b/lib/dart/lib/src/protocol/t_map.dart @@ -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. + +part of thrift; + + +class TMap { + + final int keyType; + final int valueType; + final int length; + + TMap(this.keyType, this.valueType, this.length); + +} diff --git a/lib/dart/lib/src/protocol/t_message.dart b/lib/dart/lib/src/protocol/t_message.dart new file mode 100644 index 00000000000..5c31f06cbc7 --- /dev/null +++ b/lib/dart/lib/src/protocol/t_message.dart @@ -0,0 +1,39 @@ +/// 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. + +part of thrift; + +class TMessageType { + + static const int CALL = 1; + static const int REPLY = 2; + static const int EXCEPTION = 3; + static const int ONEWAY = 4; + +} + +class TMessage { + + final String name; + final int type; + final int seqid; + + TMessage(this.name, this.type, this.seqid); + + String toString() => ""; + +} diff --git a/lib/dart/lib/src/protocol/t_protocol.dart b/lib/dart/lib/src/protocol/t_protocol.dart new file mode 100644 index 00000000000..d0b8f679f09 --- /dev/null +++ b/lib/dart/lib/src/protocol/t_protocol.dart @@ -0,0 +1,97 @@ +/// 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. + +part of thrift; + +/// Protocol interface definition +abstract class TProtocol { + + final TTransport transport; + + TProtocol(this.transport); + + /// Write + writeMessageBegin(TMessage message); + writeMessageEnd(); + + writeStructBegin(TStruct struct); + writeStructEnd(); + + writeFieldBegin(TField field); + writeFieldEnd(); + writeFieldStop(); + + writeMapBegin(TMap map); + writeMapEnd(); + + writeListBegin(TList list); + writeListEnd(); + + writeSetBegin(TSet set); + writeSetEnd(); + + writeBool(bool b); + + writeByte(int b); + + writeI16(int i16); + + writeI32(int i32); + + writeI64(num i64); + + writeDouble(num dub); + + writeString(String str); + + writeBinary(ByteBuffer bin); + + /// Read + TMessage readMessageBegin(); + readMessageEnd(); + + TStruct readStructBegin(); + readStructEnd(); + + TField readFieldBegin(); + readFieldEnd(); + + TMap readMapBegin(); + readMapEnd(); + + TList readListBegin(); + readListEnd(); + + TSet readSetBegin(); + readSetEnd(); + + bool readBool(); + + int readByte(); + + int readI16(); + + int readI32(); + + num readI64(); + + num readDouble(); + + String readString(); + + ByteBuffer readBinary(); +} diff --git a/lib/dart/lib/src/protocol/t_protocol_error.dart b/lib/dart/lib/src/protocol/t_protocol_error.dart new file mode 100644 index 00000000000..54a7ca2b50b --- /dev/null +++ b/lib/dart/lib/src/protocol/t_protocol_error.dart @@ -0,0 +1,37 @@ +/// 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. + +part of thrift; + +class TProtocolErrorType { + + static const int UNKNOWN = 0; + static const int INVALID_DATA = 1; + static const int NEGATIVE_SIZE = 2; + static const int SIZE_LIMIT = 3; + static const int BAD_VERSION = 4; + static const int NOT_IMPLEMENTED = 5; + static const int DEPTH_LIMIT = 6; + +} + +class TProtocolError extends TError { + + TProtocolError([int type = TProtocolErrorType.UNKNOWN, String message = ""]) + : super(type, message); + +} diff --git a/lib/dart/lib/src/protocol/t_protocol_util.dart b/lib/dart/lib/src/protocol/t_protocol_util.dart new file mode 100644 index 00000000000..409250136c6 --- /dev/null +++ b/lib/dart/lib/src/protocol/t_protocol_util.dart @@ -0,0 +1,110 @@ +/// 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. + +part of thrift; + + +class TProtocolUtil { + + // equal to JavaScript Number.MAX_SAFE_INTEGER, 2^53 - 1 + static const int defaultRecursionLimit = 9007199254740991; + + static int maxRecursionLimit = defaultRecursionLimit; + + static skip(TProtocol prot, int type) { + _skip(prot, type, maxRecursionLimit); + } + + static _skip(TProtocol prot, int type, int recursionLimit) { + if (recursionLimit <= 0) { + throw new TProtocolError( + TProtocolErrorType.DEPTH_LIMIT, "Depth limit exceeded"); + } + + switch (type) { + case TType.BOOL: + prot.readBool(); + break; + + case TType.BYTE: + prot.readByte(); + break; + + case TType.I16: + prot.readI16(); + break; + + case TType.I32: + prot.readI32(); + break; + + case TType.I64: + prot.readI64(); + break; + + case TType.DOUBLE: + prot.readDouble(); + break; + + case TType.STRING: + prot.readBinary(); + break; + + case TType.STRUCT: + prot.readStructBegin(); + while (true) { + TField field = prot.readFieldBegin(); + if (field.type == TType.STOP) { + break; + } + _skip(prot, field.type, recursionLimit - 1); + prot.readFieldEnd(); + } + prot.readStructEnd(); + break; + + case TType.MAP: + TMap map = prot.readMapBegin(); + for (int i = 0; i < map.length; i++) { + _skip(prot, map.keyType, recursionLimit - 1); + _skip(prot, map.valueType, recursionLimit - 1); + } + prot.readMapEnd(); + break; + + case TType.SET: + TSet set = prot.readSetBegin(); + for (int i = 0; i < set.length; i++) { + _skip(prot, set.elementType, recursionLimit - 1); + } + prot.readSetEnd(); + break; + + case TType.LIST: + TList list = prot.readListBegin(); + for (int i = 0; i < list.length; i++) { + _skip(prot, list.elementType, recursionLimit - 1); + } + prot.readListEnd(); + break; + + default: + break; + } + } + +} diff --git a/lib/dart/lib/src/protocol/t_set.dart b/lib/dart/lib/src/protocol/t_set.dart new file mode 100644 index 00000000000..585c9a2dbac --- /dev/null +++ b/lib/dart/lib/src/protocol/t_set.dart @@ -0,0 +1,28 @@ +/// 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. + +part of thrift; + + +class TSet { + + final int elementType; + final int length; + + TSet(this.elementType, this.length); + +} diff --git a/lib/dart/lib/src/protocol/t_struct.dart b/lib/dart/lib/src/protocol/t_struct.dart new file mode 100644 index 00000000000..19278f18433 --- /dev/null +++ b/lib/dart/lib/src/protocol/t_struct.dart @@ -0,0 +1,27 @@ +/// 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. + +part of thrift; + + +class TStruct { + + final String name; + + TStruct(this.name); + +} diff --git a/lib/dart/lib/src/protocol/t_type.dart b/lib/dart/lib/src/protocol/t_type.dart new file mode 100644 index 00000000000..b6d937054cc --- /dev/null +++ b/lib/dart/lib/src/protocol/t_type.dart @@ -0,0 +1,36 @@ +/// 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. + +part of thrift; + +class TType { + + static const int STOP = 0; + static const int VOID = 1; + static const int BOOL = 2; + static const int BYTE = 3; + static const int DOUBLE = 4; + static const int I16 = 6; + static const int I32 = 8; + static const int I64 = 10; + static const int STRING = 11; + static const int STRUCT = 12; + static const int MAP = 13; + static const int SET = 14; + static const int LIST = 15; + +} diff --git a/lib/dart/lib/src/t_application_error.dart b/lib/dart/lib/src/t_application_error.dart new file mode 100644 index 00000000000..5dc6f353ec5 --- /dev/null +++ b/lib/dart/lib/src/t_application_error.dart @@ -0,0 +1,107 @@ +/// 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. + +part of thrift; + +class TApplicationErrorType { + + static const int UNKNOWN = 0; + static const int UNKNOWN_METHOD = 1; + static const int INVALID_MESSAGE_TYPE = 2; + static const int WRONG_METHOD_NAME = 3; + static const int BAD_SEQUENCE_ID = 4; + static const int MISSING_RESULT = 5; + static const int INTERNAL_ERROR = 6; + static const int PROTOCOL_ERROR = 7; + static const int INVALID_TRANSFORM = 8; + static const int INVALID_PROTOCOL = 9; + static const int UNSUPPORTED_CLIENT_TYPE = 10; + +} + +class TApplicationError extends TError { + + static final TStruct _struct = new TStruct("TApplicationError"); + static const int MESSAGE = 1; + static final TField _messageField = new TField("message", TType.STRING, MESSAGE); + static const int TYPE = 2; + static final TField _typeField = new TField("type", TType.I32, TYPE); + + TApplicationError([int type = TApplicationErrorType.UNKNOWN, + String message = ""]) : super(type, message); + + static TApplicationError read(TProtocol iprot) { + TField field; + + String message = null; + int type = TApplicationErrorType.UNKNOWN; + + iprot.readStructBegin(); + while (true) { + field = iprot.readFieldBegin(); + + if (field.type == TType.STOP) { + break; + } + + switch (field.id) { + case MESSAGE: + if (field.type == TType.STRING) { + message = iprot.readString(); + } + else { + TProtocolUtil.skip(iprot, field.type); + } + break; + + case TYPE: + if (field.type == TType.I32) { + type = iprot.readI32(); + } + else { + TProtocolUtil.skip(iprot, field.type); + } + break; + + default: + TProtocolUtil.skip(iprot, field.type); + break; + } + iprot.readFieldEnd(); + } + iprot.readStructEnd(); + + return new TApplicationError(type, message); + } + + write(TProtocol oprot) { + oprot.writeStructBegin(_struct); + + if (message != null && !message.isEmpty) { + oprot.writeFieldBegin(_messageField); + oprot.writeString(message); + oprot.writeFieldEnd(); + } + + oprot.writeFieldBegin(_typeField); + oprot.writeI32(type); + oprot.writeFieldEnd(); + + oprot.writeFieldStop(); + oprot.writeStructEnd(); + } +} diff --git a/lib/dart/lib/src/t_base.dart b/lib/dart/lib/src/t_base.dart new file mode 100644 index 00000000000..56ce1f58e69 --- /dev/null +++ b/lib/dart/lib/src/t_base.dart @@ -0,0 +1,40 @@ +/// 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. + +part of thrift; + + +abstract class TBase { + + /// Reads the TObject from the given input protocol. + void read(TProtocol iprot); + + /// Writes the objects out to the [oprot] protocol. + void write(TProtocol oprot); + + /// Check if a field is currently set or unset, using the [fieldId]. + bool isSet(int fieldId); + + /// Get a field's value by [fieldId]. Primitive types will be wrapped in the + /// appropriate "boxed" types. + getFieldValue(int fieldId); + + /// Set a field's value by [fieldId]. Primitive types must be "boxed" in the + /// appropriate object wrapper type. + setFieldValue(int fieldId, Object value); + +} diff --git a/lib/dart/lib/src/t_error.dart b/lib/dart/lib/src/t_error.dart new file mode 100644 index 00000000000..58247695bf0 --- /dev/null +++ b/lib/dart/lib/src/t_error.dart @@ -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. + +part of thrift; + +class TError extends Error { + + final String message; + final int type; + + TError(this.type, this.message); + + String toString() => ""; + +} diff --git a/lib/dart/lib/src/t_processor.dart b/lib/dart/lib/src/t_processor.dart new file mode 100644 index 00000000000..dcf20fbc7d3 --- /dev/null +++ b/lib/dart/lib/src/t_processor.dart @@ -0,0 +1,24 @@ +/// 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. + +part of thrift; + +/// A processor is a generic object which operates upon an input stream and +/// writes to some output stream. +abstract class TProcessor { + bool process(TProtocol input, TProtocol output); +} diff --git a/lib/dart/lib/src/transport/t_transport.dart b/lib/dart/lib/src/transport/t_transport.dart new file mode 100644 index 00000000000..c94df7b4ff8 --- /dev/null +++ b/lib/dart/lib/src/transport/t_transport.dart @@ -0,0 +1,78 @@ +/// 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. + +part of thrift; + + +abstract class TTransport { + + /// Queries whether the transport is open. + /// Returns [true] if the transport is open. + bool isOpen(); + + /// Is there more data to be read? + /// Returns [true] if the remote side is still alive and feeding us. + bool peek() { + return isOpen(); + } + + /// Opens the transport for reading/writing. + /// Throws [TTransportError] if the transport could not be opened. + open(); + + /// Closes the transport. + close(); + + /// Reads up to [length] bytes into [buffer], starting at [offset]. + /// Returns the number of bytes actually read. + /// Throws [TTransportError] if there was an error reading data + int read(ByteBuffer buffer, int offset, int length); + + /// Guarantees that all of [length] bytes are actually read off the transport. + /// Returns the number of bytes actually read, which must be equal to + /// [length]. + /// Throws [TTransportError] if there was an error reading data + int readAll(ByteBuffer buffer, int offset, int length) { + int got = 0; + int ret = 0; + while (got < length) { + ret = read(buffer, offset+got, length-got); + if (ret <= 0) { + throw new TTransportError(TTransportErrorType.UNKNOWN, + "Cannot read. Remote side has closed. Tried to read $length " + "bytes, but only got $got bytes."); + } + got += ret; + } + return got; + } + + /// Writes the [buffer] to the output + /// Throws [TTransportError] if there was an error writing data + writeAll(ByteBuffer buffer) { + write(buffer, 0, buffer.lengthInBytes); + } + + /// Writes up to [len] bytes from the buffer. + /// Throws [TTransportError] if there was an error writing data + write(ByteBuffer buffer, int offset, int length); + + /// Flush any pending data out of a transport buffer. + /// Throws [TTransportError] if there was an error writing out data. + Future flush(); + +} diff --git a/lib/dart/lib/src/transport/t_transport_error.dart b/lib/dart/lib/src/transport/t_transport_error.dart new file mode 100644 index 00000000000..622fc02c974 --- /dev/null +++ b/lib/dart/lib/src/transport/t_transport_error.dart @@ -0,0 +1,36 @@ +/// 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. + +part of thrift; + + +class TTransportErrorType { + + static const int UNKNOWN = 0; + static const int NOT_OPEN = 1; + static const int ALREADY_OPEN = 2; + static const int TIMED_OUT = 3; + static const int END_OF_FILE = 4; + +} + +class TTransportError extends TError { + + TTransportError([int type = TTransportErrorType.UNKNOWN, String message = ""]) + : super(type, message); + +} diff --git a/lib/dart/lib/thrift.dart b/lib/dart/lib/thrift.dart new file mode 100644 index 00000000000..cad397a94a3 --- /dev/null +++ b/lib/dart/lib/thrift.dart @@ -0,0 +1,23 @@ +library thrift; + +import 'dart:async'; +import 'dart:typed_data'; + +part 'src/t_application_error.dart'; +part 'src/t_base.dart'; +part 'src/t_error.dart'; +part 'src/t_processor.dart'; + +part 'src/protocol/t_field.dart'; +part 'src/protocol/t_list.dart'; +part 'src/protocol/t_map.dart'; +part 'src/protocol/t_message.dart'; +part 'src/protocol/t_protocol.dart'; +part 'src/protocol/t_protocol_error.dart'; +part 'src/protocol/t_protocol_util.dart'; +part 'src/protocol/t_set.dart'; +part 'src/protocol/t_struct.dart'; +part 'src/protocol/t_type.dart'; + +part 'src/transport/t_transport.dart'; +part 'src/transport/t_transport_error.dart'; diff --git a/lib/dart/pubspec.yaml b/lib/dart/pubspec.yaml new file mode 100644 index 00000000000..ad9778c3139 --- /dev/null +++ b/lib/dart/pubspec.yaml @@ -0,0 +1,12 @@ +name: thrift +version: 0.1.0 +description: > + A Dart library for Apache Thrift +author: Mark Erickson +homepage: https://github.com/apache/thrift +documentation: https://github.com/apache/thrift +dependencies: + logging: '>=0.9.0 <0.12.0' +dev_dependencies: + mockito: '>=0.10.0 <0.11.0' + test: '0.12.2' From bc73baef3707be82907825c16976b6c856e5ea33 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Thu, 3 Sep 2015 23:05:30 -0500 Subject: [PATCH 04/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Minor tweaks to the dart generator --- compiler/cpp/src/generate/t_dart_generator.cc | 56 +++++++++++++------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/compiler/cpp/src/generate/t_dart_generator.cc b/compiler/cpp/src/generate/t_dart_generator.cc index b55636eceba..066a7e16320 100644 --- a/compiler/cpp/src/generate/t_dart_generator.cc +++ b/compiler/cpp/src/generate/t_dart_generator.cc @@ -63,6 +63,13 @@ class t_dart_generator : public t_oop_generator { library_name_ = ""; } + iter = parsed_options.find("skip_server"); + if (iter != parsed_options.end()) { + skip_server_ = true; + } else { + skip_server_ = false; + } + out_dir_base_ = "gen-dart"; } @@ -154,6 +161,8 @@ class t_dart_generator : public t_oop_generator { void generate_function_helpers(t_function* tfunction); std::string get_cap_name(std::string name); std::string get_field_name(std::string name); + std::string get_args_class_name(std::string name); + std::string get_result_class_name(std::string name); std::string get_file_name(std::string name); std::string generate_isset_check(t_field* field); std::string generate_isset_check(std::string field); @@ -227,12 +236,11 @@ class t_dart_generator : public t_oop_generator { std::string constant_name(std::string name); private: - /** - * File streams - */ std::ofstream f_service_; + bool skip_server_; std::string library_name_; + std::string base_dir_; std::string src_dir_; std::string library_exports_; @@ -284,9 +292,9 @@ string t_dart_generator::dart_library(string file_name) { } /** - * Prints standard Dart imports + * Prints imports for services * - * @return List of imports for Dart types that are used in here + * @return List of imports for services */ string t_dart_generator::service_imports() { return "import 'dart:async';" + endl; @@ -299,7 +307,7 @@ string t_dart_generator::service_imports() { */ string t_dart_generator::dart_thrift_imports() { return string() + - "import 'dart:typed_data' show ByteBuffer;" + endl + + "import 'dart:typed_data' show ByteData;" + endl + "import 'package:thrift/thrift.dart';" + endl + "import 'package:" + library_name_ + "/" + library_name_ + ".dart';" + endl; } @@ -635,7 +643,7 @@ void t_dart_generator::generate_dart_struct_definition(ofstream& out, out << "implements TBase"; scope_up(out); - indent(out) << "static final TStruct _STRUCT_DESC = new TStruct(\"" << tstruct->get_name() + indent(out) << "static final TStruct _STRUCT_DESC = new TStruct(\"" << class_name << "\");" << endl; // Members are public for -dart, private for -dartbean @@ -1248,7 +1256,11 @@ void t_dart_generator::generate_service(t_service* tservice) { generate_service_interface(tservice); generate_service_client(tservice); - generate_service_server(tservice); + + if (!skip_server_) { + generate_service_server(tservice); + } + generate_service_helpers(tservice); f_service_.close(); @@ -1308,10 +1320,10 @@ void t_dart_generator::generate_service_client(t_service* tservice) { string extends_client = ""; if (tservice->get_extends() != NULL) { extends = tservice->get_extends()->get_name(); - extends_client = " extends " + extends + "Impl"; + extends_client = " extends " + extends + "Client"; } - string class_name = service_name_ + "Impl"; + string class_name = service_name_ + "Client"; export_class_to_library(get_file_name(service_name_), class_name); indent(f_service_) << "class " << class_name << extends_client << " extends " << service_name_; @@ -1352,7 +1364,7 @@ void t_dart_generator::generate_service_client(t_service* tservice) { // Get the struct of function call params t_struct* arg_struct = (*f_iter)->get_arglist(); - string argsname = (*f_iter)->get_name() + "_args"; + string argsname = get_args_class_name((*f_iter)->get_name()); vector::const_iterator fld_iter; const vector& fields = arg_struct->get_members(); @@ -1381,7 +1393,7 @@ void t_dart_generator::generate_service_client(t_service* tservice) { indent(f_service_) << "throw error;" << endl; scope_down(f_service_, endl2); - string result_class = (*f_iter)->get_name() + "_result"; + string result_class = get_result_class_name((*f_iter)->get_name()); indent(f_service_) << result_class << " result = new " << result_class << "();" << endl; indent(f_service_) << "result.read(_iprot);" << endl; indent(f_service_) << "_iprot.readMessageEnd();" << endl; @@ -1507,7 +1519,7 @@ void t_dart_generator::generate_function_helpers(t_function* tfunction) { return; } - t_struct result(program_, tfunction->get_name() + "_result"); + t_struct result(program_, get_result_class_name(tfunction->get_name())); t_field success(tfunction->get_returntype(), "success", 0); if (!tfunction->get_returntype()->is_void()) { result.append(&success); @@ -1539,8 +1551,8 @@ void t_dart_generator::generate_process_function(t_service* tservice, t_function } scope_up(f_service_); - string argsname = tfunction->get_name() + "_args"; - string resultname = tfunction->get_name() + "_result"; + string argsname = get_args_class_name(tfunction->get_name()); + string resultname = get_result_class_name(tfunction->get_name()); indent(f_service_) << argsname << " args = new " << argsname << "();" << endl; indent(f_service_) << "args.read(iprot);" << endl; @@ -2014,7 +2026,7 @@ string t_dart_generator::base_type_name(t_base_type* type, bool in_container) { return "void"; case t_base_type::TYPE_STRING: if (type->is_binary()) { - return "ByteBuffer"; + return "ByteData"; } else { return "String"; } @@ -2171,6 +2183,14 @@ std::string t_dart_generator::get_field_name(std::string name) { return name; } +std::string t_dart_generator::get_args_class_name(std::string name) { + return name + "_args"; +} + +std::string t_dart_generator::get_result_class_name(std::string name) { + return name + "_result"; +} + std::string t_dart_generator::get_file_name(std::string name) { // e.g. change APIForFileIO to api_for_file_io @@ -2276,4 +2296,6 @@ std::string t_dart_generator::get_enum_class_name(t_type* type) { THRIFT_REGISTER_GENERATOR( dart, "Dart", - " library_name=thrift Optional override for library name.\n"); + " library_name=thrift Optional override for library name.\n" + " skip_server Skip generation of service server code.\n" +); From fc049c05cf617f6c689b40c278288e48a85e9371 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Thu, 3 Sep 2015 23:09:24 -0500 Subject: [PATCH 05/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Added binary and JSON protocols. Added WebSocket transport. --- lib/dart/.gitignore | 1 + lib/dart/lib/src/html/t_web_socket.dart | 137 ++++ .../lib/src/protocol/t_binary_protocol.dart | 256 +++++++ .../lib/src/protocol/t_json_protocol.dart | 721 ++++++++++++++++++ lib/dart/lib/src/protocol/t_protocol.dart | 61 +- lib/dart/lib/src/protocol/t_struct.dart | 2 +- lib/dart/lib/src/transport/t_socket.dart | 42 + .../lib/src/transport/t_socket_transport.dart | 93 +++ lib/dart/lib/src/transport/t_transport.dart | 44 +- lib/dart/lib/thrift.dart | 11 +- lib/dart/lib/thrift_html.dart | 3 + lib/dart/pubspec.yaml | 9 +- 12 files changed, 1319 insertions(+), 61 deletions(-) create mode 100644 lib/dart/lib/src/html/t_web_socket.dart create mode 100644 lib/dart/lib/src/protocol/t_binary_protocol.dart create mode 100644 lib/dart/lib/src/protocol/t_json_protocol.dart create mode 100644 lib/dart/lib/src/transport/t_socket.dart create mode 100644 lib/dart/lib/src/transport/t_socket_transport.dart create mode 100644 lib/dart/lib/thrift_html.dart diff --git a/lib/dart/.gitignore b/lib/dart/.gitignore index 6854399b721..acda527ef2a 100644 --- a/lib/dart/.gitignore +++ b/lib/dart/.gitignore @@ -1,3 +1,4 @@ +.packages packages .pub/ pubspec.lock diff --git a/lib/dart/lib/src/html/t_web_socket.dart b/lib/dart/lib/src/html/t_web_socket.dart new file mode 100644 index 00000000000..e7674dbeb5a --- /dev/null +++ b/lib/dart/lib/src/html/t_web_socket.dart @@ -0,0 +1,137 @@ +/// 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. + +library thrift.src.html; + +import 'dart:async'; +import 'dart:convert' show Utf8Codec; +import 'dart:html' show CloseEvent; +import 'dart:html' show Event; +import 'dart:html' show MessageEvent; +import 'dart:html' show WebSocket; + +import 'package:thrift/thrift.dart'; + + +/// A [TSocket] backed by a [WebSocket] from dart:html +class TWebSocket implements TSocket { + + static const utf8Codec = const Utf8Codec(); + + final Uri url; + + final StreamController _onStateController; + Stream get onState => _onStateController.stream; + + final StreamController _onErrorController; + Stream get onError => _onErrorController.stream; + + final List>> _completers = []; + final List<_Send> _sendPending = []; + + TWebSocket(this.url) : + _onStateController = new StreamController.broadcast(), + _onErrorController = new StreamController.broadcast() { + if (url == null || !url.hasAuthority || !url.hasPort) { + throw new TTransportError(TTransportErrorType.NOT_OPEN, "Invalid url"); + } + } + + WebSocket _socket; + + bool get isOpen => _socket != null && _socket.readyState == WebSocket.OPEN; + + bool get isClosed => _socket == null || _socket.readyState == WebSocket.CLOSED; + + void open() { + if (!isClosed) { + throw new TTransportError(TTransportErrorType.ALREADY_OPEN, + "Socket already connected"); + } + + _socket = new WebSocket(url.toString()); + _socket.onError.listen(_onError); + _socket.onOpen.listen(_onOpen); + _socket.onClose.listen(_onClose); + _socket.onMessage.listen(_onMessage); + } + + void close() { + if (_socket != null) { + _socket.close(); + } + } + + Future> send(List data) { + Completer> completer = new Completer(); + + if (!isOpen) { + _sendPending.add(new _Send(data, completer)); + } else { + _send(completer, data); + } + + return completer.future; + } + + void _send(Completer> completer, List data) { + _completers.add(completer); + _socket.sendString(utf8Codec.decode(data)); + } + + void _onOpen(Event event) { + _onStateController.add(TSocketState.OPEN); + + while (_sendPending.length > 0) { + _Send s = _sendPending.removeAt(0); + _send(s.completer, s.data); + } + } + + void _onClose(CloseEvent event) { + _socket = null; + _completers.clear(); + _sendPending.clear(); + + _onStateController.add(TSocketState.CLOSED); + } + + void _onMessage(MessageEvent event) { + List data; + + if (event.data is String) { + data = utf8Codec.encode(event.data); + } else { + data = event.data; + } + + if (!_completers.isEmpty) { + _completers.removeAt(0).complete(data); + } + } + + void _onError(Event event) { + _onErrorController.add(event.toString()); + } +} + +class _Send { + final List data; + final Completer> completer; + + _Send(this.data, this.completer); +} diff --git a/lib/dart/lib/src/protocol/t_binary_protocol.dart b/lib/dart/lib/src/protocol/t_binary_protocol.dart new file mode 100644 index 00000000000..88bcb069202 --- /dev/null +++ b/lib/dart/lib/src/protocol/t_binary_protocol.dart @@ -0,0 +1,256 @@ +/// 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. + +part of thrift; + +/// Binary protocol implementation for Thrift. +/// +/// Adapted from the C# version. +class TBinaryProtocol extends TProtocol { + + static const int VERSION_MASK = 0xffff0000; + static const int VERSION_1 = 0x80010000; + + static const Utf8Codec _utf8Codec = const Utf8Codec(); + + final bool strictRead; + final bool strictWrite; + + + TBinaryProtocol(TTransport transport, {this.strictRead: false, this.strictWrite: true}) : super(transport); + + /// write + void writeMessageBegin(TMessage message) { + if (strictWrite) { + int version = VERSION_1 | message.type; + writeI32(version); + writeString(message.name); + writeI32(message.seqid); + } else { + writeString(message.name); + writeByte(message.type); + writeI32(message.seqid); + } + } + + void writeMessageEnd() {} + + void writeStructBegin(TStruct struct) {} + + void writeStructEnd() {} + + void writeFieldBegin(TField field) { + writeByte(field.type); + writeI16(field.id); + } + + void writeFieldEnd() {} + + void writeFieldStop() { + writeByte(TType.STOP); + } + + void writeMapBegin(TMap map) { + writeByte(map.keyType); + writeByte(map.valueType); + writeI32(map.length); + } + + void writeMapEnd() {} + + void writeListBegin(TList list) { + writeByte(list.elementType); + writeI32(list.length); + } + + void writeListEnd() {} + + void writeSetBegin(TSet set) { + writeByte(set.elementType); + writeI32(set.length); + } + + void writeSetEnd() {} + + void writeBool(bool b) { + writeByte(b ? 1 : 0); + } + + final ByteData _byteOut = new ByteData(1); + void writeByte(int byte) { + _byteOut.setUint8(0, byte); + transport.write(_byteOut.buffer.asUint8List(), 0, 1); + } + + final ByteData _i16Out = new ByteData(2); + void writeI16(int i16) { + _i16Out.setInt16(0, i16); + transport.write(_i16Out.buffer.asUint8List(), 0, 2); + } + + final ByteData _i32Out = new ByteData(4); + void writeI32(int i32) { + _i32Out.setInt32(0, i32); + transport.write(_i32Out.buffer.asUint8List(), 0, 4); + } + + final ByteData _i64Out = new ByteData(8); + void writeI64(int i64) { + _i64Out.setInt64(0, i64); + transport.write(_i64Out.buffer.asUint8List(), 0, 8); + } + + void writeString(String s) { + var bytes = _utf8Codec.encode(s); + writeI32(bytes.length); + transport.write(bytes, 0, bytes.length); + } + + final ByteData _doubleOut = new ByteData(8); + void writeDouble(double d) { + _doubleOut.setFloat64(0, d); + transport.write(_doubleOut.buffer.asUint8List(), 0, 8); + } + + void writeBinary(ByteData bytes) { + writeI32(bytes.lengthInBytes); + transport.write(bytes.buffer.asUint8List(), 0, bytes.lengthInBytes); + } + + /// read + TMessage readMessageBegin() { + String name; + int type; + int seqid; + + int size = readI32(); + if (size < 0) { + int version = size & VERSION_MASK; + if (version != VERSION_1) { + throw new TProtocolError(TProtocolErrorType.BAD_VERSION, "Bad version in readMessageBegin: $version"); + } + type = size & 0x000000ff; + name = readString(); + seqid = readI32(); + } else { + if (strictRead) { + throw new TProtocolError(TProtocolErrorType.BAD_VERSION, "Missing version in readMessageBegin"); + } + name = _readString(size); + type = readByte(); + seqid = readI32(); + } + return new TMessage(name, type, seqid); + } + + void readMessageEnd() {} + + TStruct readStructBegin() { + return new TStruct(); + } + + void readStructEnd() {} + + TField readFieldBegin() { + String name = ""; + int type = readByte(); + int id = type != TType.STOP ? readI16() : 0; + + return new TField(name, type, id); + } + + void readFieldEnd() {} + + TMap readMapBegin() { + int keyType = readByte(); + int valueType = readByte(); + int length = readI32(); + + return new TMap(keyType, valueType, length); + } + + void readMapEnd() {} + + TList readListBegin() { + int elementType = readByte(); + int length = readI32(); + + return new TList(elementType, length); + } + + void readListEnd() {} + + TSet readSetBegin() { + int elementType = readByte(); + int length = readI32(); + + return new TSet(elementType, length); + } + + void readSetEnd() {} + + bool readBool() => readByte() == 1; + + Uint8List _byteIn = new Uint8List(1); + int readByte() { + transport.readAll(_byteIn, 0, 1); + return _byteIn.buffer.asByteData().getUint8(0); + } + + Uint8List _i16In = new Uint8List(2); + int readI16() { + transport.readAll(_i16In, 0, 2); + return _i16In.buffer.asByteData().getUint16(0); + } + + Uint8List _i32In = new Uint8List(4); + int readI32() { + transport.readAll(_i32In, 0, 4); + return _i32In.buffer.asByteData().getUint32(0); + } + + Uint8List _i64In = new Uint8List(8); + int readI64() { + transport.readAll(_i64In, 0, 8); + return _i64In.buffer.asByteData().getUint64(0); + } + + Uint8List _doubleIn = new Uint8List(8); + double readDouble() { + transport.readAll(_doubleIn, 0, 8); + return _doubleIn.buffer.asByteData().getFloat64(0); + } + + String readString() { + int size = readI32(); + return _readString(size); + } + + String _readString(int size) { + Uint8List stringIn = new Uint8List(size); + transport.readAll(stringIn, 0, size); + return _utf8Codec.decode(stringIn); + } + + ByteData readBinary() { + int size = readI32(); + Uint8List binaryIn = new Uint8List(size); + transport.readAll(binaryIn, 0, size); + return binaryIn.buffer.asByteData(); + } + +} diff --git a/lib/dart/lib/src/protocol/t_json_protocol.dart b/lib/dart/lib/src/protocol/t_json_protocol.dart new file mode 100644 index 00000000000..da93a73484c --- /dev/null +++ b/lib/dart/lib/src/protocol/t_json_protocol.dart @@ -0,0 +1,721 @@ +/// 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. + +part of thrift; + +/// JSON protocol implementation for Thrift. +/// +/// Adapted from the C# version. +class TJsonProtocol extends TProtocol { + + static const int VERSION_1 = 1; + + static const Utf8Codec utf8Codec = const Utf8Codec(); + + _BaseContext _context; + _LookaheadReader _reader; + + List<_BaseContext> contextStack = []; + final List _tempBuffer = new List(4); + + TJsonProtocol(TTransport transport) : super(transport) { + _context = new _BaseContext(this); + _reader = new _LookaheadReader(this); + } + + void _pushContext(_BaseContext c) { + contextStack.add(c); + _context = c; + } + + void _popContext() { + _context = contextStack.removeLast(); + } + + /// Read a byte that must match [char]; otherwise throw a [TProtocolError]. + void readJsonSyntaxChar(String char) { + int charByte = char.codeUnitAt(0); + int byte = _reader.read(); + if (byte != charByte) { + throw new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Unexpected character: ${new String.fromCharCode(byte)}"); + } + } + + int _hexVal(int byte) { + if (byte >= _Constants.HEX_0.codeUnitAt(0) && + byte <= _Constants.HEX_9.codeUnitAt(0)) { + return byte - _Constants.HEX_0.codeUnitAt(0); + } else if (byte >= _Constants.HEX_A.codeUnitAt(0) && + byte <= _Constants.HEX_F.codeUnitAt(0)) { + byte += 10; + return byte - _Constants.HEX_A.codeUnitAt(0); + } else { + throw new TProtocolError( + TProtocolErrorType.INVALID_DATA, "Expected hex character"); + } + } + + int _hexChar(int byte) { + return byte.toRadixString(16).codeUnitAt(0); + } + + /// write + + /// Write the [bytes] as JSON characters, escaping as needed. + void _writeJsonString(List bytes) { + _context.write(); + transport.writeAll(_Constants.QUOTE.codeUnits); + + for (int i = 0; i < bytes.length; i++) { + int byte = bytes[i]; + if ((byte & 0x00FF) >= 0x30) { + if (byte == _Constants.BACKSLASH.codeUnitAt(0)) { + transport.writeAll(_Constants.BACKSLASH.codeUnits); + transport.writeAll(_Constants.BACKSLASH.codeUnits); + } else { + transport.write(bytes, i, 1); + } + } else { + _tempBuffer[0] = _Constants.JSON_CHAR_TABLE[byte]; + if (_tempBuffer[0] == 1) { + transport.write(bytes, i, 1); + } else if (_tempBuffer[0] > 1) { + transport.write(_Constants.BACKSLASH.codeUnits, i, 1); + transport.write(_tempBuffer, 0, 1); + } else { + transport.writeAll(_Constants.ESCSEQ.codeUnits); + _tempBuffer[0] = _hexChar(byte >> 4); + _tempBuffer[1] = _hexChar(byte); + transport.write(_tempBuffer, 0, 2); + } + } + } + + transport.writeAll(_Constants.QUOTE.codeUnits); + } + + void _writeJsonInteger(int i) { + _context.write(); + String str = i.toString(); + + if (_context.escapeNumbers) { + transport.writeAll(_Constants.QUOTE.codeUnits); + } + transport.writeAll(utf8Codec.encode(str)); + if (_context.escapeNumbers) { + transport.writeAll(_Constants.QUOTE.codeUnits); + } + } + + void _writeJsonDouble(double d) { + _context.write(); + String str = d.toString(); + bool escapeNumbers = d.isNaN || d.isInfinite || _context.escapeNumbers; + + if (escapeNumbers) { + transport.writeAll(_Constants.QUOTE.codeUnits); + } + transport.writeAll(utf8Codec.encode(str)); + if (escapeNumbers) { + transport.writeAll(_Constants.QUOTE.codeUnits); + } + } + + void _writeJsonBase64(List bytes) { + _context.write(); + transport.writeAll(_Constants.QUOTE.codeUnits); + + String base64 = CryptoUtils.bytesToBase64(bytes); + transport.writeAll(utf8Codec.encode(base64)); + + transport.writeAll(_Constants.QUOTE.codeUnits); + } + + void _writeJsonObjectStart() { + _context.write(); + transport.writeAll(_Constants.LBRACE.codeUnits); + _pushContext(new _PairContext(this)); + } + + void _writeJsonObjectEnd() { + _popContext(); + transport.writeAll(_Constants.RBRACE.codeUnits); + } + + void _writeJsonArrayStart() { + _context.write(); + transport.writeAll(_Constants.LBRACKET.codeUnits); + _pushContext(new _ListContext(this)); + } + + void _writeJsonArrayEnd() { + _popContext(); + transport.writeAll(_Constants.RBRACKET.codeUnits); + } + + void writeMessageBegin(TMessage message) { + _writeJsonArrayStart(); + _writeJsonInteger(VERSION_1); + + _writeJsonString(utf8Codec.encode(message.name)); + _writeJsonInteger(message.type); + _writeJsonInteger(message.seqid); + } + + void writeMessageEnd() { + _writeJsonArrayEnd(); + } + + void writeStructBegin(TStruct struct) { + _writeJsonObjectStart(); + } + + void writeStructEnd() { + _writeJsonObjectEnd(); + } + + void writeFieldBegin(TField field) { + _writeJsonInteger(field.id); + _writeJsonObjectStart(); + _writeJsonString(_Constants.getTypeNameForTypeId(field.type).codeUnits); + } + + void writeFieldEnd() { + _writeJsonObjectEnd(); + } + + void writeFieldStop() {} + + void writeMapBegin(TMap map) { + _writeJsonArrayStart(); + _writeJsonString(_Constants.getTypeNameForTypeId(map.keyType).codeUnits); + _writeJsonString(_Constants.getTypeNameForTypeId(map.valueType).codeUnits); + _writeJsonInteger(map.length); + _writeJsonObjectStart(); + } + + void writeMapEnd() { + _writeJsonObjectEnd(); + _writeJsonArrayEnd(); + } + + void writeListBegin(TList list) { + _writeJsonArrayStart(); + _writeJsonString( + _Constants.getTypeNameForTypeId(list.elementType).codeUnits); + _writeJsonInteger(list.length); + } + + void writeListEnd() { + _writeJsonArrayEnd(); + } + + void writeSetBegin(TSet set) { + _writeJsonArrayStart(); + _writeJsonString( + _Constants.getTypeNameForTypeId(set.elementType).codeUnits); + _writeJsonInteger(set.length); + } + + void writeSetEnd() { + _writeJsonArrayEnd(); + } + + void writeBool(bool b) { + _writeJsonInteger(b ? 1 : 0); + } + + void writeByte(int b) { + _writeJsonInteger(b); + } + + void writeI16(int i16) { + _writeJsonInteger(i16); + } + + void writeI32(int i32) { + _writeJsonInteger(i32); + } + + void writeI64(int i64) { + _writeJsonInteger(i64); + } + + void writeDouble(double d) { + _writeJsonDouble(d); + } + + void writeString(String s) { + _writeJsonString(utf8Codec.encode(s)); + } + + void writeBinary(ByteData bytes) { + _writeJsonBase64(bytes.buffer.asUint8List()); + } + + + /// read + + List _readJsonString({bool skipContext: false}) { + List bytes = []; + + if (!skipContext) { + _context.read(); + } + + readJsonSyntaxChar(_Constants.QUOTE); + while (true) { + int byte = _reader.read(); + if (byte == _Constants.QUOTE.codeUnitAt(0)) { + break; + } + + // escaped? + if (byte != _Constants.ESCSEQ.codeUnitAt(0)) { + bytes.add(byte); + continue; + } + + byte = _reader.read(); + + // distinguish between \u00XX and control chars like \n + if (byte != _Constants.ESCSEQ.codeUnitAt(1)) { + String char = new String.fromCharCode(byte); + int offset = _Constants.ESCAPE_CHARS.indexOf(char); + if (offset == -1) { + throw new TProtocolError( + TProtocolErrorType.INVALID_DATA, "Expected control char"); + } + byte = _Constants.ESCAPE_CHAR_VALS.codeUnitAt(offset); + bytes.add(byte); + continue; + } + + // it's \u00XX + readJsonSyntaxChar(_Constants.HEX_0); + readJsonSyntaxChar(_Constants.HEX_0); + transport.readAll(_tempBuffer, 0, 2); + byte = _hexVal(_tempBuffer[0]) << 4 + _hexVal(_tempBuffer[1]); + bytes.add(byte); + } + + return bytes; + } + + String _readJsonNumericChars() { + StringBuffer buffer = new StringBuffer(); + while (true) { + int byte = _reader.peek(); + if (!_Constants.isJsonNumeric(byte)) { + break; + } + buffer.write(new String.fromCharCode(byte)); + } + return buffer.toString(); + } + + int _readJsonInteger() { + _context.read(); + + if (_context.escapeNumbers) { + readJsonSyntaxChar(_Constants.QUOTE); + } + String str = _readJsonNumericChars(); + if (_context.escapeNumbers) { + readJsonSyntaxChar(_Constants.QUOTE); + } + + try { + return int.parse(str); + } on FormatException catch(_) { + throw new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Bad data encounted in numeric data"); + } + } + + double _readJsonDouble() { + _context.read(); + + if (_reader.peek() == _Constants.QUOTE.codeUnitAt(0)) { + List bytes = _readJsonString(skipContext: true); + double d = double.parse(utf8Codec.decode(bytes), (_) { + throw new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Bad data encounted in numeric data"); + }); + if (!_context.escapeNumbers && !d.isNaN && !d.isInfinite) { + throw new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Numeric data unexpectedly quoted"); + } + return d; + } else { + if (_context.escapeNumbers) { + // This will throw - we should have had a quote if escapeNumbers == true + readJsonSyntaxChar(_Constants.QUOTE); + } + return double.parse(_readJsonNumericChars(), (_) { + throw new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Bad data encounted in numeric data"); + }); + } + } + + List _readJsonBase64() { + List bytes = _readJsonString(); + String base64 = utf8Codec.decode(bytes); + return CryptoUtils.base64StringToBytes(base64); + } + + void _readJsonObjectStart() { + _context.read(); + readJsonSyntaxChar(_Constants.LBRACE); + _pushContext(new _PairContext(this)); + } + + void _readJsonObjectEnd() { + readJsonSyntaxChar(_Constants.RBRACE); + _popContext(); + } + + void _readJsonArrayStart() { + _context.read(); + readJsonSyntaxChar(_Constants.LBRACKET); + _pushContext(new _ListContext(this)); + } + + void _readJsonArrayEnd() { + readJsonSyntaxChar(_Constants.RBRACKET); + _popContext(); + } + + TMessage readMessageBegin() { + _readJsonArrayStart(); + if (_readJsonInteger() != VERSION_1) { + throw new TProtocolError(TProtocolErrorType.BAD_VERSION, + "Message contained bad version."); + } + + List buffer = _readJsonString(); + String name = utf8Codec.decode(buffer); + int type = _readJsonInteger(); + int seqid = _readJsonInteger(); + + return new TMessage(name, type, seqid); + } + + void readMessageEnd() { + _readJsonArrayEnd(); + } + + TStruct readStructBegin() { + _readJsonObjectStart(); + return new TStruct(); + } + + void readStructEnd() { + _readJsonObjectEnd(); + } + + TField readFieldBegin() { + int byte = _reader.peek(); + + String name = ""; + int type = TType.STOP; + int id = 0; + + if (byte != _Constants.RBRACE.codeUnitAt(0)) { + id = _readJsonInteger(); + type = _Constants.getTypeIdForTypeName(_readJsonString()); + } + + return new TField(name, type, id); + } + + void readFieldEnd() { + _readJsonObjectEnd(); + } + + TMap readMapBegin() { + _readJsonArrayStart(); + int keyType = _Constants.getTypeIdForTypeName(_readJsonString()); + int valueType = _Constants.getTypeIdForTypeName(_readJsonString()); + int length = _readJsonInteger(); + _readJsonObjectStart(); + + return new TMap(keyType, valueType, length); + } + + void readMapEnd() { + _readJsonObjectEnd(); + _readJsonArrayEnd(); + } + + TList readListBegin() { + _readJsonArrayStart(); + int elementType = _Constants.getTypeIdForTypeName(_readJsonString()); + int length = _readJsonInteger(); + + return new TList(elementType, length); + } + + void readListEnd() { + _readJsonArrayEnd(); + } + + TSet readSetBegin() { + _readJsonArrayStart(); + int elementType = _Constants.getTypeIdForTypeName(_readJsonString()); + int length = _readJsonInteger(); + + return new TSet(elementType, length); + } + + void readSetEnd() { + _readJsonArrayEnd(); + } + + bool readBool() { + return _readJsonInteger() == 0 ? false : true; + } + + int readByte() { + return _readJsonInteger(); + } + + int readI16() { + return _readJsonInteger(); + } + + int readI32() { + return _readJsonInteger(); + } + + int readI64() { + return _readJsonInteger(); + } + + double readDouble() { + return _readJsonDouble(); + } + + String readString() { + return utf8Codec.decode(_readJsonString()); + } + + ByteData readBinary() { + return new Uint8List.fromList(_readJsonBase64()).buffer.asByteData(); + } + +} + +class _Constants { + + static const utf8codec = const Utf8Codec(); + + static const String HEX_0 = '0'; + static const String HEX_9 = '9'; + static const String HEX_A = 'a'; + static const String HEX_F = 'f'; + static const String COMMA = ','; + static const String COLON = ':'; + static const String LBRACE = '{'; + static const String RBRACE = '}'; + static const String LBRACKET = '['; + static const String RBRACKET = ']'; + static const String QUOTE = '"'; + static const String BACKSLASH = r'\'; + + static const String ESCSEQ = r'\u00'; + + static final List JSON_CHAR_TABLE = new List.unmodifiable([ + 0, 0, 0, 0, 0, 0, 0, 0, 'b'.codeUnitAt(0), 't'.codeUnitAt(0), 'n'.codeUnitAt(0), 0, 'f'.codeUnitAt(0), 'r'.codeUnitAt(0), 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, '"'.codeUnitAt(0), 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ]); + + static const String ESCAPE_CHARS = r'"\/bfnrt'; + static const String ESCAPE_CHAR_VALS = '"\\/\b\f\n\r\t'; + + static const String NAME_BOOL = 'tf'; + static const String NAME_BYTE = 'i8'; + static const String NAME_I16 = 'i16'; + static const String NAME_I32 = 'i32'; + static const String NAME_I64 = 'i64'; + static const String NAME_DOUBLE = 'dbl'; + static const String NAME_STRUCT = 'rec'; + static const String NAME_STRING = 'str'; + static const String NAME_MAP = 'map'; + static const String NAME_LIST = 'lst'; + static const String NAME_SET = 'set'; + + static final Map _TYPE_ID_TO_NAME = new Map.unmodifiable({ + TType.BOOL: NAME_BOOL, + TType.BYTE: NAME_BYTE, + TType.I16: NAME_I16, + TType.I32: NAME_I32, + TType.I64: NAME_I64, + TType.DOUBLE: NAME_DOUBLE, + TType.STRING: NAME_STRING, + TType.STRUCT: NAME_STRUCT, + TType.MAP: NAME_MAP, + TType.SET: NAME_SET, + TType.LIST: NAME_LIST + }); + + static String getTypeNameForTypeId(int typeId) { + if (!_TYPE_ID_TO_NAME.containsKey(typeId)) { + throw new TProtocolError( + TProtocolErrorType.NOT_IMPLEMENTED, "Unrecognized type"); + } + + return _TYPE_ID_TO_NAME[typeId]; + } + + static final Map, int> _NAME_TO_TYPE_ID = new Map.unmodifiable({ + NAME_BOOL.codeUnits: TType.BOOL, + NAME_BYTE.codeUnits: TType.BYTE, + NAME_I16.codeUnits: TType.I16, + NAME_I32.codeUnits: TType.I32, + NAME_I64.codeUnits: TType.I64, + NAME_DOUBLE.codeUnits: TType.DOUBLE, + NAME_STRING.codeUnits: TType.STRING, + NAME_STRUCT.codeUnits: TType.STRUCT, + NAME_MAP.codeUnits: TType.MAP, + NAME_SET.codeUnits: TType.SET, + NAME_LIST.codeUnits: TType.LIST + }); + + static int getTypeIdForTypeName(List bytes) { + String name = utf8codec.decode(bytes); + if (!_NAME_TO_TYPE_ID.containsKey(name)) { + throw new TProtocolError( + TProtocolErrorType.NOT_IMPLEMENTED, "Unrecognized type"); + } + + return _NAME_TO_TYPE_ID[name]; + } + + static final Set _JSON_NUMERICS = new Set.from([ + '+', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'E', 'e' + ].map((String s) => s.codeUnitAt(0))); + + static bool isJsonNumeric(int byte) { + return _JSON_NUMERICS.contains(byte); + } + +} + +class _LookaheadReader { + + final TJsonProtocol protocol; + + _LookaheadReader(this.protocol); + + bool _hasData; + final List _data = new List(1); + + int read() { + if (_hasData) { + _hasData = false; + } else { + protocol.transport.readAll(_data, 0, 1); + } + + return _data[0]; + } + + int peek() { + if (!_hasData) { + protocol.transport.readAll(_data, 0, 1); + } + _hasData = true; + + return _data[0]; + } + +} + +class _BaseContext { + + final TJsonProtocol protocol; + + _BaseContext(this.protocol); + + void write() {} + + void read() {} + + bool get escapeNumbers => false; + +} + +class _ListContext extends _BaseContext { + + _ListContext(TJsonProtocol protocol) : super(protocol); + + bool _first = true; + + void write() { + if (_first) { + _first = false; + } else { + protocol.transport.writeAll(_Constants.COMMA.codeUnits); + } + } + + void read() { + if (_first) { + _first = false; + } else { + protocol.readJsonSyntaxChar(_Constants.COMMA); + } + } + +} + +class _PairContext extends _BaseContext { + + _PairContext(TJsonProtocol protocol) : super(protocol); + + bool _first = true; + bool _colon = true; + + String get symbol => _colon ? _Constants.COLON : _Constants.COMMA; + + void write() { + if (_first) { + _first = false; + _colon = true; + } else { + protocol.transport.writeAll(symbol.codeUnits); + _colon = !_colon; + } + } + + void read() { + if (_first) { + _first = false; + _colon = true; + } else { + protocol.readJsonSyntaxChar(symbol); + _colon = !_colon; + } + } + + bool get escapeNumbers => _colon; + +} diff --git a/lib/dart/lib/src/protocol/t_protocol.dart b/lib/dart/lib/src/protocol/t_protocol.dart index d0b8f679f09..b8639846467 100644 --- a/lib/dart/lib/src/protocol/t_protocol.dart +++ b/lib/dart/lib/src/protocol/t_protocol.dart @@ -17,7 +17,6 @@ part of thrift; -/// Protocol interface definition abstract class TProtocol { final TTransport transport; @@ -25,59 +24,59 @@ abstract class TProtocol { TProtocol(this.transport); /// Write - writeMessageBegin(TMessage message); - writeMessageEnd(); + void writeMessageBegin(TMessage message); + void writeMessageEnd(); - writeStructBegin(TStruct struct); - writeStructEnd(); + void writeStructBegin(TStruct struct); + void writeStructEnd(); - writeFieldBegin(TField field); - writeFieldEnd(); - writeFieldStop(); + void writeFieldBegin(TField field); + void writeFieldEnd(); + void writeFieldStop(); - writeMapBegin(TMap map); - writeMapEnd(); + void writeMapBegin(TMap map); + void writeMapEnd(); - writeListBegin(TList list); - writeListEnd(); + void writeListBegin(TList list); + void writeListEnd(); - writeSetBegin(TSet set); - writeSetEnd(); + void writeSetBegin(TSet set); + void writeSetEnd(); - writeBool(bool b); + void writeBool(bool b); - writeByte(int b); + void writeByte(int b); - writeI16(int i16); + void writeI16(int i16); - writeI32(int i32); + void writeI32(int i32); - writeI64(num i64); + void writeI64(int i64); - writeDouble(num dub); + void writeDouble(double d); - writeString(String str); + void writeString(String str); - writeBinary(ByteBuffer bin); + void writeBinary(ByteData bytes); /// Read TMessage readMessageBegin(); - readMessageEnd(); + void readMessageEnd(); TStruct readStructBegin(); - readStructEnd(); + void readStructEnd(); TField readFieldBegin(); - readFieldEnd(); + void readFieldEnd(); TMap readMapBegin(); - readMapEnd(); + void readMapEnd(); TList readListBegin(); - readListEnd(); + void readListEnd(); TSet readSetBegin(); - readSetEnd(); + void readSetEnd(); bool readBool(); @@ -87,11 +86,11 @@ abstract class TProtocol { int readI32(); - num readI64(); + int readI64(); - num readDouble(); + double readDouble(); String readString(); - ByteBuffer readBinary(); + ByteData readBinary(); } diff --git a/lib/dart/lib/src/protocol/t_struct.dart b/lib/dart/lib/src/protocol/t_struct.dart index 19278f18433..fe0f5fa4b5c 100644 --- a/lib/dart/lib/src/protocol/t_struct.dart +++ b/lib/dart/lib/src/protocol/t_struct.dart @@ -22,6 +22,6 @@ class TStruct { final String name; - TStruct(this.name); + TStruct([this.name=""]); } diff --git a/lib/dart/lib/src/transport/t_socket.dart b/lib/dart/lib/src/transport/t_socket.dart new file mode 100644 index 00000000000..4dc16ff614e --- /dev/null +++ b/lib/dart/lib/src/transport/t_socket.dart @@ -0,0 +1,42 @@ +/// 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. + +part of thrift; + + +enum TSocketState { + CLOSED, + OPEN +} + +abstract class TSocket { + + Stream get onState; + + Stream get onError; + + bool get isOpen; + + bool get isClosed; + + void open(); + + void close(); + + Future> send(List data); + +} diff --git a/lib/dart/lib/src/transport/t_socket_transport.dart b/lib/dart/lib/src/transport/t_socket_transport.dart new file mode 100644 index 00000000000..5ee7fd82e4a --- /dev/null +++ b/lib/dart/lib/src/transport/t_socket_transport.dart @@ -0,0 +1,93 @@ +/// 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. + +part of thrift; + +/// Socket implementation of [TTransport]. +/// +/// Adapted from the JS Socket version. +class TSocketTransport extends TTransport { + + final TSocket socket; + final Logger log = new Logger('thrift.TSocketTransport'); + + final List _sendBuffer = []; + + Iterator _dataIterator; + + TSocketTransport(this.socket) { + socket.onError.listen((String e) => log.warning(e)); + } + + bool get isOpen => socket.isOpen; + + Future flush() async { + List request = new List.from(_sendBuffer, growable: false); + _sendBuffer.clear(); + + List result = await socket.send(request); + _dataIterator = result.iterator; + } + + void open() { + socket.open(); + } + + void close() { + socket.close(); + } + + int read(List buffer, int offset, int length) { + if (buffer == null) { + throw new ArgumentError.notNull("buffer"); + } + + if (offset + length > buffer.length) { + throw new ArgumentError("The range exceeds the buffer length"); + } + + if (_dataIterator == null || length <= 0) { + return 0; + } + + int i = 0; + while (i < length && _dataIterator.moveNext()) { + buffer[offset + i] = _dataIterator.current; + i++; + } + + // cleanup iterator when we've reached the end + if (_dataIterator.current == null) { + _dataIterator = null; + } + + return i; + } + + void write(List buffer, int offset, int length) { + if (buffer == null) { + throw new ArgumentError.notNull("buffer"); + } + + if (offset + length > buffer.length) { + throw new ArgumentError("The range exceeds the buffer length"); + } + + _sendBuffer.addAll(buffer.sublist(offset, offset + length)); + } + +} diff --git a/lib/dart/lib/src/transport/t_transport.dart b/lib/dart/lib/src/transport/t_transport.dart index c94df7b4ff8..7a75c8a894b 100644 --- a/lib/dart/lib/src/transport/t_transport.dart +++ b/lib/dart/lib/src/transport/t_transport.dart @@ -22,54 +22,48 @@ abstract class TTransport { /// Queries whether the transport is open. /// Returns [true] if the transport is open. - bool isOpen(); - - /// Is there more data to be read? - /// Returns [true] if the remote side is still alive and feeding us. - bool peek() { - return isOpen(); - } + bool get isOpen; /// Opens the transport for reading/writing. /// Throws [TTransportError] if the transport could not be opened. - open(); + void open(); /// Closes the transport. - close(); + void close(); /// Reads up to [length] bytes into [buffer], starting at [offset]. /// Returns the number of bytes actually read. /// Throws [TTransportError] if there was an error reading data - int read(ByteBuffer buffer, int offset, int length); + int read(List buffer, int offset, int length); /// Guarantees that all of [length] bytes are actually read off the transport. /// Returns the number of bytes actually read, which must be equal to /// [length]. /// Throws [TTransportError] if there was an error reading data - int readAll(ByteBuffer buffer, int offset, int length) { + int readAll(List buffer, int offset, int length) { int got = 0; - int ret = 0; - while (got < length) { - ret = read(buffer, offset+got, length-got); - if (ret <= 0) { - throw new TTransportError(TTransportErrorType.UNKNOWN, - "Cannot read. Remote side has closed. Tried to read $length " - "bytes, but only got $got bytes."); - } - got += ret; + int ret = 0; + while (got < length) { + ret = read(buffer, offset+got, length-got); + if (ret <= 0) { + throw new TTransportError(TTransportErrorType.UNKNOWN, + "Cannot read. Remote side has closed. Tried to read $length " + "bytes, but only got $got bytes."); } - return got; + got += ret; } + return got; + } - /// Writes the [buffer] to the output + /// Writes the [bytes] to the output. /// Throws [TTransportError] if there was an error writing data - writeAll(ByteBuffer buffer) { - write(buffer, 0, buffer.lengthInBytes); + void writeAll(List buffer) { + write(buffer, 0, buffer.length); } /// Writes up to [len] bytes from the buffer. /// Throws [TTransportError] if there was an error writing data - write(ByteBuffer buffer, int offset, int length); + void write(List buffer, int offset, int length); /// Flush any pending data out of a transport buffer. /// Throws [TTransportError] if there was an error writing out data. diff --git a/lib/dart/lib/thrift.dart b/lib/dart/lib/thrift.dart index cad397a94a3..e547d1ad963 100644 --- a/lib/dart/lib/thrift.dart +++ b/lib/dart/lib/thrift.dart @@ -1,14 +1,21 @@ library thrift; import 'dart:async'; -import 'dart:typed_data'; +import 'dart:convert' show Utf8Codec; +import 'dart:typed_data' show ByteData; +import 'dart:typed_data' show Uint8List; + +import 'package:crypto/crypto.dart' show CryptoUtils; +import 'package:logging/logging.dart'; part 'src/t_application_error.dart'; part 'src/t_base.dart'; part 'src/t_error.dart'; part 'src/t_processor.dart'; +part 'src/protocol/t_binary_protocol.dart'; part 'src/protocol/t_field.dart'; +part 'src/protocol/t_json_protocol.dart'; part 'src/protocol/t_list.dart'; part 'src/protocol/t_map.dart'; part 'src/protocol/t_message.dart'; @@ -19,5 +26,7 @@ part 'src/protocol/t_set.dart'; part 'src/protocol/t_struct.dart'; part 'src/protocol/t_type.dart'; +part 'src/transport/t_socket.dart'; part 'src/transport/t_transport.dart'; part 'src/transport/t_transport_error.dart'; +part 'src/transport/t_socket_transport.dart'; diff --git a/lib/dart/lib/thrift_html.dart b/lib/dart/lib/thrift_html.dart new file mode 100644 index 00000000000..7569a79a945 --- /dev/null +++ b/lib/dart/lib/thrift_html.dart @@ -0,0 +1,3 @@ +library thrift_html; + +export 'src/html/t_web_socket.dart' show TWebSocket; diff --git a/lib/dart/pubspec.yaml b/lib/dart/pubspec.yaml index ad9778c3139..d38d703067c 100644 --- a/lib/dart/pubspec.yaml +++ b/lib/dart/pubspec.yaml @@ -6,7 +6,10 @@ author: Mark Erickson homepage: https://github.com/apache/thrift documentation: https://github.com/apache/thrift dependencies: - logging: '>=0.9.0 <0.12.0' + crypto: '>=0.9.0' + http: '>=0.11.3' + logging: '>=0.11.0' dev_dependencies: - mockito: '>=0.10.0 <0.11.0' - test: '0.12.2' + test: '>=0.12.0' +environment: + sdk: '>=0.12.0' From 495c0dd068c92e1125256e42ac2f355786f53799 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Fri, 4 Sep 2015 13:18:03 -0500 Subject: [PATCH 06/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Switch service generation of service server code to opt-in instead of opt-out. --- compiler/cpp/src/generate/t_dart_generator.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/cpp/src/generate/t_dart_generator.cc b/compiler/cpp/src/generate/t_dart_generator.cc index 066a7e16320..5d8a2bfc81c 100644 --- a/compiler/cpp/src/generate/t_dart_generator.cc +++ b/compiler/cpp/src/generate/t_dart_generator.cc @@ -63,11 +63,11 @@ class t_dart_generator : public t_oop_generator { library_name_ = ""; } - iter = parsed_options.find("skip_server"); + iter = parsed_options.find("gen_server"); if (iter != parsed_options.end()) { - skip_server_ = true; + gen_server_ = true; } else { - skip_server_ = false; + gen_server_ = false; } out_dir_base_ = "gen-dart"; @@ -238,7 +238,7 @@ class t_dart_generator : public t_oop_generator { private: std::ofstream f_service_; - bool skip_server_; + bool gen_server_; std::string library_name_; std::string base_dir_; @@ -1257,7 +1257,7 @@ void t_dart_generator::generate_service(t_service* tservice) { generate_service_interface(tservice); generate_service_client(tservice); - if (!skip_server_) { + if (gen_server_) { generate_service_server(tservice); } @@ -2297,5 +2297,5 @@ THRIFT_REGISTER_GENERATOR( dart, "Dart", " library_name=thrift Optional override for library name.\n" - " skip_server Skip generation of service server code.\n" + " gen_server Generate service server classes.\n" ); From ecce9715401834bafeb94e23c9dd4772be066204 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Fri, 4 Sep 2015 13:37:16 -0500 Subject: [PATCH 07/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Added THttpTransport and a did little refactoring. --- lib/dart/README.md | 2 +- lib/dart/lib/src/html/t_web_socket.dart | 33 ++++--- .../lib/src/protocol/t_json_protocol.dart | 9 +- .../lib/src/transport/t_async_transport.dart | 67 ++++++++++++++ .../lib/src/transport/t_http_transport.dart | 88 +++++++++++++++++++ .../lib/src/transport/t_socket_transport.dart | 64 ++++---------- lib/dart/lib/thrift.dart | 3 + 7 files changed, 198 insertions(+), 68 deletions(-) create mode 100644 lib/dart/lib/src/transport/t_async_transport.dart create mode 100644 lib/dart/lib/src/transport/t_http_transport.dart diff --git a/lib/dart/README.md b/lib/dart/README.md index 44ae70257fc..2be168ba00a 100644 --- a/lib/dart/README.md +++ b/lib/dart/README.md @@ -23,4 +23,4 @@ under the License. Using Thrift with Dart ==================== -Dart 1.11.3 or newer is required +Dart 1.12.0 or newer is required diff --git a/lib/dart/lib/src/html/t_web_socket.dart b/lib/dart/lib/src/html/t_web_socket.dart index e7674dbeb5a..375d71e70c0 100644 --- a/lib/dart/lib/src/html/t_web_socket.dart +++ b/lib/dart/lib/src/html/t_web_socket.dart @@ -41,13 +41,13 @@ class TWebSocket implements TSocket { Stream get onError => _onErrorController.stream; final List>> _completers = []; - final List<_Send> _sendPending = []; + final List<_Request> _requests = []; TWebSocket(this.url) : _onStateController = new StreamController.broadcast(), _onErrorController = new StreamController.broadcast() { if (url == null || !url.hasAuthority || !url.hasPort) { - throw new TTransportError(TTransportErrorType.NOT_OPEN, "Invalid url"); + throw new ArgumentError("Invalid url"); } } @@ -79,33 +79,29 @@ class TWebSocket implements TSocket { Future> send(List data) { Completer> completer = new Completer(); - if (!isOpen) { - _sendPending.add(new _Send(data, completer)); - } else { - _send(completer, data); - } + _requests.add(new _Request(data, completer)); + _sendRequests(); return completer.future; } - void _send(Completer> completer, List data) { - _completers.add(completer); - _socket.sendString(utf8Codec.decode(data)); + void _sendRequests() { + while (isOpen && _requests.length > 0) { + _Request request = _requests.removeAt(0); + _completers.add(request.completer); + _socket.sendString(utf8Codec.decode(request.data)); + } } void _onOpen(Event event) { _onStateController.add(TSocketState.OPEN); - - while (_sendPending.length > 0) { - _Send s = _sendPending.removeAt(0); - _send(s.completer, s.data); - } + _sendRequests(); } void _onClose(CloseEvent event) { _socket = null; _completers.clear(); - _sendPending.clear(); + _requests.clear(); _onStateController.add(TSocketState.CLOSED); } @@ -125,13 +121,14 @@ class TWebSocket implements TSocket { } void _onError(Event event) { + close(); _onErrorController.add(event.toString()); } } -class _Send { +class _Request { final List data; final Completer> completer; - _Send(this.data, this.completer); + _Request(this.data, this.completer); } diff --git a/lib/dart/lib/src/protocol/t_json_protocol.dart b/lib/dart/lib/src/protocol/t_json_protocol.dart index da93a73484c..a49d3e3bd8d 100644 --- a/lib/dart/lib/src/protocol/t_json_protocol.dart +++ b/lib/dart/lib/src/protocol/t_json_protocol.dart @@ -542,9 +542,12 @@ class _Constants { static const String ESCSEQ = r'\u00'; static final List JSON_CHAR_TABLE = new List.unmodifiable([ - 0, 0, 0, 0, 0, 0, 0, 0, 'b'.codeUnitAt(0), 't'.codeUnitAt(0), 'n'.codeUnitAt(0), 0, 'f'.codeUnitAt(0), 'r'.codeUnitAt(0), 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, '"'.codeUnitAt(0), 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + 0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes + 'b'.codeUnitAt(0), 't'.codeUnitAt(0), 'n'.codeUnitAt(0), 0, 'f'.codeUnitAt(0), 'r'.codeUnitAt(0), 0, 0, // 8 bytes + 0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes + 0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes + 1, 1, '"'.codeUnitAt(0), 1, 1, 1, 1, 1, // 8 bytes + 1, 1, 1, 1, 1, 1, 1, 1 // 8 bytes ]); static const String ESCAPE_CHARS = r'"\/bfnrt'; diff --git a/lib/dart/lib/src/transport/t_async_transport.dart b/lib/dart/lib/src/transport/t_async_transport.dart new file mode 100644 index 00000000000..50c902e595a --- /dev/null +++ b/lib/dart/lib/src/transport/t_async_transport.dart @@ -0,0 +1,67 @@ +/// 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. + +part of thrift; + +/// Socket implementation of [TTransport]. +/// +/// Adapted from the JS WebSocket transport. +abstract class TAsyncTransport extends TTransport { + + final List _sendBuffer = []; + Iterator _dataIterator; + + int read(List buffer, int offset, int length) { + if (buffer == null) { + throw new ArgumentError.notNull("buffer"); + } + + if (offset + length > buffer.length) { + throw new ArgumentError("The range exceeds the buffer length"); + } + + if (_dataIterator == null || length <= 0) { + return 0; + } + + int i = 0; + while (i < length && _dataIterator.moveNext()) { + buffer[offset + i] = _dataIterator.current; + i++; + } + + // cleanup iterator when we've reached the end + if (_dataIterator.current == null) { + _dataIterator = null; + } + + return i; + } + + void write(List buffer, int offset, int length) { + if (buffer == null) { + throw new ArgumentError.notNull("buffer"); + } + + if (offset + length > buffer.length) { + throw new ArgumentError("The range exceeds the buffer length"); + } + + _sendBuffer.addAll(buffer.sublist(offset, offset + length)); + } + +} diff --git a/lib/dart/lib/src/transport/t_http_transport.dart b/lib/dart/lib/src/transport/t_http_transport.dart new file mode 100644 index 00000000000..8762874abb7 --- /dev/null +++ b/lib/dart/lib/src/transport/t_http_transport.dart @@ -0,0 +1,88 @@ +/// 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. + +part of thrift; + +/// HTTP implementation of [TTransport]. +/// +/// For example: +/// +/// var transport = new THttpTransport(url, new BrowserClient(), { +/// 'X-My-Custom-Header': 'my value' +/// }); +/// var protocol = new TJsonProtocol(transport); +/// var client = new MyThriftServiceClient(protocol); +/// var result = client.myMethod(); +/// +/// Adapted from the JS XHR HTTP transport. +class THttpTransport extends TAsyncTransport { + + final Uri url; + final http.Client httpClient; + + HttpConfig _config; + + THttpTransport(this.url, this.httpClient, {Map headers}) { + if (url == null || !url.hasAuthority) { + throw new ArgumentError("Invalid url"); + } + if (httpClient == null) { + throw new ArgumentError.notNull("httpClient"); + } + + _config = new HttpConfig(headers: headers); + } + + bool get isOpen => true; + + void open() {} + + void close() { + httpClient.close(); + } + + Future flush() async { + http.Response response = await httpClient.post( + url, headers: _config.headers, body: _sendBuffer); + _dataIterator = response.bodyBytes.iterator; + } + +} + +class HttpConfig { + + final Map _baseHeaders; + + Map _headers; + get headers => _headers; + + HttpConfig({Map headers}) : _baseHeaders = headers { + _initHeaders(); + } + + void _initHeaders() { + _headers = {}; + + if (_baseHeaders != null) { + _headers.addAll(_baseHeaders); + } + + _headers['Content-Type'] = 'application/x-thrift'; + _headers['Accept'] = 'application/x-thrift'; + } + +} diff --git a/lib/dart/lib/src/transport/t_socket_transport.dart b/lib/dart/lib/src/transport/t_socket_transport.dart index 5ee7fd82e4a..7f8b09e50fe 100644 --- a/lib/dart/lib/src/transport/t_socket_transport.dart +++ b/lib/dart/lib/src/transport/t_socket_transport.dart @@ -19,8 +19,15 @@ part of thrift; /// Socket implementation of [TTransport]. /// -/// Adapted from the JS Socket version. -class TSocketTransport extends TTransport { +/// For example: +/// +/// var transport = new TSocketTransport(new TWebSocket(url)); +/// var protocol = new Thrift.Protocol(transport); +/// var client = new MyThriftServiceClient(protocol); +/// var result = client.myMethod(); +/// +/// Adapted from the JS WebSocket transport. +class TSocketTransport extends TAsyncTransport { final TSocket socket; final Logger log = new Logger('thrift.TSocketTransport'); @@ -30,19 +37,15 @@ class TSocketTransport extends TTransport { Iterator _dataIterator; TSocketTransport(this.socket) { + if (socket == null) { + throw new ArgumentError.notNull("socket"); + } + socket.onError.listen((String e) => log.warning(e)); } bool get isOpen => socket.isOpen; - Future flush() async { - List request = new List.from(_sendBuffer, growable: false); - _sendBuffer.clear(); - - List result = await socket.send(request); - _dataIterator = result.iterator; - } - void open() { socket.open(); } @@ -51,43 +54,12 @@ class TSocketTransport extends TTransport { socket.close(); } - int read(List buffer, int offset, int length) { - if (buffer == null) { - throw new ArgumentError.notNull("buffer"); - } - - if (offset + length > buffer.length) { - throw new ArgumentError("The range exceeds the buffer length"); - } - - if (_dataIterator == null || length <= 0) { - return 0; - } - - int i = 0; - while (i < length && _dataIterator.moveNext()) { - buffer[offset + i] = _dataIterator.current; - i++; - } - - // cleanup iterator when we've reached the end - if (_dataIterator.current == null) { - _dataIterator = null; - } - - return i; - } - - void write(List buffer, int offset, int length) { - if (buffer == null) { - throw new ArgumentError.notNull("buffer"); - } - - if (offset + length > buffer.length) { - throw new ArgumentError("The range exceeds the buffer length"); - } + Future flush() async { + List request = new List.from(_sendBuffer, growable: false); + _sendBuffer.clear(); - _sendBuffer.addAll(buffer.sublist(offset, offset + length)); + List result = await socket.send(request); + _dataIterator = result.iterator; } } diff --git a/lib/dart/lib/thrift.dart b/lib/dart/lib/thrift.dart index e547d1ad963..b30953307d0 100644 --- a/lib/dart/lib/thrift.dart +++ b/lib/dart/lib/thrift.dart @@ -6,6 +6,7 @@ import 'dart:typed_data' show ByteData; import 'dart:typed_data' show Uint8List; import 'package:crypto/crypto.dart' show CryptoUtils; +import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; part 'src/t_application_error.dart'; @@ -26,6 +27,8 @@ part 'src/protocol/t_set.dart'; part 'src/protocol/t_struct.dart'; part 'src/protocol/t_type.dart'; +part 'src/transport/t_async_transport.dart'; +part 'src/transport/t_http_transport.dart'; part 'src/transport/t_socket.dart'; part 'src/transport/t_transport.dart'; part 'src/transport/t_transport_error.dart'; From a01e06af071927f878c39b4c1f9b44c177fd775f Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Fri, 4 Sep 2015 15:28:42 -0500 Subject: [PATCH 08/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Added BufferedTransport and FramedTransport, with some refactoring. --- .../lib/src/protocol/t_binary_protocol.dart | 10 +-- ...ansport.dart => t_buffered_transport.dart} | 62 +++++++++++--- .../lib/src/transport/t_framed_transport.dart | 83 +++++++++++++++++++ .../lib/src/transport/t_http_transport.dart | 60 +++++++------- .../lib/src/transport/t_socket_transport.dart | 17 ++-- lib/dart/lib/thrift.dart | 5 +- 6 files changed, 175 insertions(+), 62 deletions(-) rename lib/dart/lib/src/transport/{t_async_transport.dart => t_buffered_transport.dart} (54%) create mode 100644 lib/dart/lib/src/transport/t_framed_transport.dart diff --git a/lib/dart/lib/src/protocol/t_binary_protocol.dart b/lib/dart/lib/src/protocol/t_binary_protocol.dart index 88bcb069202..2d1aa837a10 100644 --- a/lib/dart/lib/src/protocol/t_binary_protocol.dart +++ b/lib/dart/lib/src/protocol/t_binary_protocol.dart @@ -205,31 +205,31 @@ class TBinaryProtocol extends TProtocol { bool readBool() => readByte() == 1; - Uint8List _byteIn = new Uint8List(1); + final Uint8List _byteIn = new Uint8List(1); int readByte() { transport.readAll(_byteIn, 0, 1); return _byteIn.buffer.asByteData().getUint8(0); } - Uint8List _i16In = new Uint8List(2); + final Uint8List _i16In = new Uint8List(2); int readI16() { transport.readAll(_i16In, 0, 2); return _i16In.buffer.asByteData().getUint16(0); } - Uint8List _i32In = new Uint8List(4); + final Uint8List _i32In = new Uint8List(4); int readI32() { transport.readAll(_i32In, 0, 4); return _i32In.buffer.asByteData().getUint32(0); } - Uint8List _i64In = new Uint8List(8); + final Uint8List _i64In = new Uint8List(8); int readI64() { transport.readAll(_i64In, 0, 8); return _i64In.buffer.asByteData().getUint64(0); } - Uint8List _doubleIn = new Uint8List(8); + final Uint8List _doubleIn = new Uint8List(8); double readDouble() { transport.readAll(_doubleIn, 0, 8); return _doubleIn.buffer.asByteData().getFloat64(0); diff --git a/lib/dart/lib/src/transport/t_async_transport.dart b/lib/dart/lib/src/transport/t_buffered_transport.dart similarity index 54% rename from lib/dart/lib/src/transport/t_async_transport.dart rename to lib/dart/lib/src/transport/t_buffered_transport.dart index 50c902e595a..21c1cfcd2e3 100644 --- a/lib/dart/lib/src/transport/t_async_transport.dart +++ b/lib/dart/lib/src/transport/t_buffered_transport.dart @@ -17,13 +17,47 @@ part of thrift; -/// Socket implementation of [TTransport]. -/// -/// Adapted from the JS WebSocket transport. -abstract class TAsyncTransport extends TTransport { +/// Buffered implementation of [TTransport]. +class TBufferedTransport extends TTransport { + + final List _writeBuffer = []; + Iterator _readIterator; + + List _consumeWriteBuffer() { + List buffer = new List.from(_writeBuffer, growable: false); + _writeBuffer.clear(); + return buffer; + } + + void _setReadBuffer(List readBuffer) { + _readIterator = readBuffer != null ? readBuffer.iterator : null; + } + + void _reset() { + _writeBuffer.clear(); + _readIterator = null; + } + + bool get hasReadData => _readIterator != null; - final List _sendBuffer = []; - Iterator _dataIterator; + int get writeBufferLength => _writeBuffer.length; + + bool _isOpen; + bool get isOpen => _isOpen; + + void open() { + if (isOpen) { + throw new TTransportError(TTransportErrorType.ALREADY_OPEN, + "The transport is already open"); + } + _isOpen = true; + _reset(); + } + + void close() { + _isOpen = false; + _reset(); + } int read(List buffer, int offset, int length) { if (buffer == null) { @@ -34,19 +68,19 @@ abstract class TAsyncTransport extends TTransport { throw new ArgumentError("The range exceeds the buffer length"); } - if (_dataIterator == null || length <= 0) { + if (_readIterator == null || length <= 0) { return 0; } int i = 0; - while (i < length && _dataIterator.moveNext()) { - buffer[offset + i] = _dataIterator.current; + while (i < length && _readIterator.moveNext()) { + buffer[offset + i] = _readIterator.current; i++; } // cleanup iterator when we've reached the end - if (_dataIterator.current == null) { - _dataIterator = null; + if (_readIterator.current == null) { + _readIterator = null; } return i; @@ -61,7 +95,11 @@ abstract class TAsyncTransport extends TTransport { throw new ArgumentError("The range exceeds the buffer length"); } - _sendBuffer.addAll(buffer.sublist(offset, offset + length)); + _writeBuffer.addAll(buffer.sublist(offset, offset + length)); + } + + Future flush() { + return new Future.value(); } } diff --git a/lib/dart/lib/src/transport/t_framed_transport.dart b/lib/dart/lib/src/transport/t_framed_transport.dart new file mode 100644 index 00000000000..d7fcb62b627 --- /dev/null +++ b/lib/dart/lib/src/transport/t_framed_transport.dart @@ -0,0 +1,83 @@ +/// 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. + +part of thrift; + +/// Framed [TTransport]. +/// +/// Adapted from the Java Framed transport. +class TFramedTransport extends TBufferedTransport { + + static const I32_LENGTH = 4; + + final TTransport _transport; + + final Uint8List headerBytes = new Uint8List(I32_LENGTH); + + TFramedTransport(TTransport transport) : _transport = transport { + if (transport == null) { + throw new ArgumentError.notNull("transport"); + } + } + + bool get isOpen => _transport.isOpen; + + void open() { + super.open(); + _transport.open(); + } + + void close() { + super.close(); + _transport.close(); + } + + int read(List buffer, int offset, int length) { + if (hasReadData) { + int got = super.read(buffer, offset, length); + if (got > 0) return got; + } + + _readFrame(); + + return super.read(buffer, offset, length); + } + + void _readFrame() { + _transport.readAll(headerBytes, 0, I32_LENGTH); + int size = headerBytes.buffer.asByteData().getUint32(0); + + if (size < 0) { + throw new TTransportError(TTransportErrorType.UNKNOWN, + "Read a negative frame size: $size"); + } + + List buffer = new List(size); + _transport.readAll(buffer, 0, size); + _setReadBuffer(buffer); + } + + Future flush() { + List buffer = _consumeWriteBuffer(); + + headerBytes.buffer.asByteData().setUint32(0, buffer.length); + _transport.writeAll(headerBytes); + _transport.writeAll(buffer); + + return _transport.flush(); + } +} diff --git a/lib/dart/lib/src/transport/t_http_transport.dart b/lib/dart/lib/src/transport/t_http_transport.dart index 8762874abb7..e4986ada63a 100644 --- a/lib/dart/lib/src/transport/t_http_transport.dart +++ b/lib/dart/lib/src/transport/t_http_transport.dart @@ -21,68 +21,64 @@ part of thrift; /// /// For example: /// -/// var transport = new THttpTransport(url, new BrowserClient(), { -/// 'X-My-Custom-Header': 'my value' -/// }); -/// var protocol = new TJsonProtocol(transport); +/// var transport = new THttpClientTransport(new BrowserClient(), +/// new THttpConfig(url, {'X-My-Custom-Header': 'my value'})); +/// var protocol = new TJsonProtocol(transport); /// var client = new MyThriftServiceClient(protocol); /// var result = client.myMethod(); /// /// Adapted from the JS XHR HTTP transport. -class THttpTransport extends TAsyncTransport { +class THttpClientTransport extends TBufferedTransport { - final Uri url; - final http.Client httpClient; - - HttpConfig _config; + final http.BrowserClient httpClient; + final THttpConfig config; - THttpTransport(this.url, this.httpClient, {Map headers}) { - if (url == null || !url.hasAuthority) { - throw new ArgumentError("Invalid url"); - } + THttpClientTransport(this.httpClient, this.config) { if (httpClient == null) { throw new ArgumentError.notNull("httpClient"); } - - _config = new HttpConfig(headers: headers); } - bool get isOpen => true; - - void open() {} - void close() { + super.close(); httpClient.close(); } Future flush() async { - http.Response response = await httpClient.post( - url, headers: _config.headers, body: _sendBuffer); - _dataIterator = response.bodyBytes.iterator; + var body = _consumeWriteBuffer(); + var response = await httpClient.post( + config.url, headers: config.headers, body: body); + _setReadBuffer(response.bodyBytes); } } -class HttpConfig { +class THttpConfig { - final Map _baseHeaders; + final Uri url; Map _headers; get headers => _headers; - HttpConfig({Map headers}) : _baseHeaders = headers { - _initHeaders(); + THttpConfig(this.url, Map headers) { + if (url == null || !url.hasAuthority) { + throw new ArgumentError("Invalid url"); + } + + _initHeaders(headers); } - void _initHeaders() { - _headers = {}; + void _initHeaders(Map initial) { + var h = {}; - if (_baseHeaders != null) { - _headers.addAll(_baseHeaders); + if (initial != null) { + h.addAll(initial); } - _headers['Content-Type'] = 'application/x-thrift'; - _headers['Accept'] = 'application/x-thrift'; + h['Content-Type'] = 'application/x-thrift'; + h['Accept'] = 'application/x-thrift'; + + _headers = new Map.unmodifiable(h); } } diff --git a/lib/dart/lib/src/transport/t_socket_transport.dart b/lib/dart/lib/src/transport/t_socket_transport.dart index 7f8b09e50fe..dcceea59b95 100644 --- a/lib/dart/lib/src/transport/t_socket_transport.dart +++ b/lib/dart/lib/src/transport/t_socket_transport.dart @@ -22,20 +22,16 @@ part of thrift; /// For example: /// /// var transport = new TSocketTransport(new TWebSocket(url)); -/// var protocol = new Thrift.Protocol(transport); +/// var protocol = new TBinaryProtocol(transport); /// var client = new MyThriftServiceClient(protocol); /// var result = client.myMethod(); /// /// Adapted from the JS WebSocket transport. -class TSocketTransport extends TAsyncTransport { +class TSocketTransport extends TBufferedTransport { final TSocket socket; final Logger log = new Logger('thrift.TSocketTransport'); - final List _sendBuffer = []; - - Iterator _dataIterator; - TSocketTransport(this.socket) { if (socket == null) { throw new ArgumentError.notNull("socket"); @@ -47,19 +43,18 @@ class TSocketTransport extends TAsyncTransport { bool get isOpen => socket.isOpen; void open() { + super.open(); socket.open(); } void close() { + super.close(); socket.close(); } Future flush() async { - List request = new List.from(_sendBuffer, growable: false); - _sendBuffer.clear(); - - List result = await socket.send(request); - _dataIterator = result.iterator; + List result = await socket.send(_consumeWriteBuffer()); + _setReadBuffer(result); } } diff --git a/lib/dart/lib/thrift.dart b/lib/dart/lib/thrift.dart index b30953307d0..0786bd04beb 100644 --- a/lib/dart/lib/thrift.dart +++ b/lib/dart/lib/thrift.dart @@ -6,7 +6,7 @@ import 'dart:typed_data' show ByteData; import 'dart:typed_data' show Uint8List; import 'package:crypto/crypto.dart' show CryptoUtils; -import 'package:http/http.dart' as http; +import 'package:http/browser_client.dart' as http; import 'package:logging/logging.dart'; part 'src/t_application_error.dart'; @@ -27,7 +27,8 @@ part 'src/protocol/t_set.dart'; part 'src/protocol/t_struct.dart'; part 'src/protocol/t_type.dart'; -part 'src/transport/t_async_transport.dart'; +part 'src/transport/t_buffered_transport.dart'; +part 'src/transport/t_framed_transport.dart'; part 'src/transport/t_http_transport.dart'; part 'src/transport/t_socket.dart'; part 'src/transport/t_transport.dart'; From 84e8763249b4233a0c18e69848eb86eaabcde638 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Fri, 4 Sep 2015 16:14:56 -0500 Subject: [PATCH 09/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Added MultiplexedProtocol. --- .../src/protocol/t_multiplexed_protocol.dart | 45 ++++++ .../src/protocol/t_protocol_decorator.dart | 143 ++++++++++++++++++ .../src/transport/t_buffered_transport.dart | 2 - lib/dart/lib/thrift.dart | 2 + 4 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 lib/dart/lib/src/protocol/t_multiplexed_protocol.dart create mode 100644 lib/dart/lib/src/protocol/t_protocol_decorator.dart diff --git a/lib/dart/lib/src/protocol/t_multiplexed_protocol.dart b/lib/dart/lib/src/protocol/t_multiplexed_protocol.dart new file mode 100644 index 00000000000..7a27a65b831 --- /dev/null +++ b/lib/dart/lib/src/protocol/t_multiplexed_protocol.dart @@ -0,0 +1,45 @@ +/// 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. + +part of thrift; + +/// Adapted from the C# version. +class TMultiplexedProtocol extends TProtocolDecorator { + + static const SEPARATOR = ':'; + + final String _serviceName; + + TMultiplexedProtocol(TProtocol protocol, String serviceName) + : _serviceName = serviceName, super(protocol) { + if (serviceName == null) { + throw new ArgumentError.notNull("serviceName"); + } + } + + + void writeMessageBegin(TMessage message) { + if (message.type == TMessageType.CALL || + message.type == TMessageType.ONEWAY) { + String name = _serviceName + SEPARATOR + message.name; + message = new TMessage(name, message.type, message.seqid); + } + + super.writeMessageBegin(message); + } + +} diff --git a/lib/dart/lib/src/protocol/t_protocol_decorator.dart b/lib/dart/lib/src/protocol/t_protocol_decorator.dart new file mode 100644 index 00000000000..eee2e83ad92 --- /dev/null +++ b/lib/dart/lib/src/protocol/t_protocol_decorator.dart @@ -0,0 +1,143 @@ +/// 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. + +part of thrift; + +/// Forward all operations to the wrapped protocol. Used as a base class. +/// +/// Adapted from the C# version. +class TProtocolDecorator extends TProtocol { + + final TProtocol _protocol; + + TProtocolDecorator(TProtocol protocol) + : _protocol = protocol, super(protocol.transport); + + /// Write + + void writeMessageBegin(TMessage message) { + _protocol.writeMessageBegin(message); + } + void writeMessageEnd() { + _protocol.writeMessageEnd(); + } + + void writeStructBegin(TStruct struct) { + _protocol.writeStructBegin(struct); + } + void writeStructEnd() { + _protocol.writeStructEnd(); + } + + void writeFieldBegin(TField field) { + _protocol.writeFieldBegin(field); + } + void writeFieldEnd() { + _protocol.writeFieldEnd(); + } + void writeFieldStop() { + _protocol.writeFieldStop(); + } + + void writeMapBegin(TMap map) { + _protocol.writeMapBegin(map); + } + void writeMapEnd() { + _protocol.writeMapEnd(); + } + + void writeListBegin(TList list) { + _protocol.writeListBegin(list); + } + void writeListEnd() { + _protocol.writeListEnd(); + } + + void writeSetBegin(TSet set) { + _protocol.writeSetBegin(set); + } + void writeSetEnd() { + _protocol.writeSetEnd(); + } + + void writeBool(bool b) { + _protocol.writeBool(b); + } + + void writeByte(int b) { + _protocol.writeByte(b); + } + + void writeI16(int i16) { + _protocol.writeI16(i16); + } + + void writeI32(int i32) { + _protocol.writeI32(i32); + } + + void writeI64(int i64) { + _protocol.writeI64(i64); + } + + void writeDouble(double d) { + _protocol.writeDouble(d); + } + + void writeString(String str) { + _protocol.writeString(str); + } + + void writeBinary(ByteData bytes) { + _protocol.writeBinary(bytes); + } + + /// Read + TMessage readMessageBegin() => _protocol.readMessageBegin(); + void readMessageEnd() => _protocol.readMessageEnd(); + + TStruct readStructBegin() => _protocol.readStructBegin(); + void readStructEnd() => _protocol.readStructEnd(); + + TField readFieldBegin() => _protocol.readFieldBegin(); + void readFieldEnd() => _protocol.readFieldEnd(); + + TMap readMapBegin() => _protocol.readMapBegin(); + void readMapEnd() => _protocol.readMapEnd(); + + TList readListBegin() => _protocol.readListBegin(); + void readListEnd() => _protocol.readListEnd(); + + TSet readSetBegin() => _protocol.readSetBegin(); + void readSetEnd() => _protocol.readSetEnd(); + + bool readBool() => _protocol.readBool(); + + int readByte() => _protocol.readByte(); + + int readI16() => _protocol.readI16(); + + int readI32() => _protocol.readI32(); + + int readI64() => _protocol.readI64(); + + double readDouble() => _protocol.readDouble(); + + String readString() => _protocol.readString(); + + ByteData readBinary() => _protocol.readBinary(); +} diff --git a/lib/dart/lib/src/transport/t_buffered_transport.dart b/lib/dart/lib/src/transport/t_buffered_transport.dart index 21c1cfcd2e3..55369f41418 100644 --- a/lib/dart/lib/src/transport/t_buffered_transport.dart +++ b/lib/dart/lib/src/transport/t_buffered_transport.dart @@ -40,8 +40,6 @@ class TBufferedTransport extends TTransport { bool get hasReadData => _readIterator != null; - int get writeBufferLength => _writeBuffer.length; - bool _isOpen; bool get isOpen => _isOpen; diff --git a/lib/dart/lib/thrift.dart b/lib/dart/lib/thrift.dart index 0786bd04beb..3d01aec76d0 100644 --- a/lib/dart/lib/thrift.dart +++ b/lib/dart/lib/thrift.dart @@ -20,7 +20,9 @@ part 'src/protocol/t_json_protocol.dart'; part 'src/protocol/t_list.dart'; part 'src/protocol/t_map.dart'; part 'src/protocol/t_message.dart'; +part 'src/protocol/t_multiplexed_protocol.dart'; part 'src/protocol/t_protocol.dart'; +part 'src/protocol/t_protocol_decorator.dart'; part 'src/protocol/t_protocol_error.dart'; part 'src/protocol/t_protocol_util.dart'; part 'src/protocol/t_set.dart'; From 08d0ce2fdf915c7f23fb8e48a92ffdd16589e16d Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Fri, 4 Sep 2015 16:25:08 -0500 Subject: [PATCH 10/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Ran dartfmt. --- lib/dart/lib/src/html/t_web_socket.dart | 15 +++-- .../lib/src/protocol/t_binary_protocol.dart | 13 +++-- lib/dart/lib/src/protocol/t_field.dart | 2 - .../lib/src/protocol/t_json_protocol.dart | 58 ++++++++++--------- lib/dart/lib/src/protocol/t_list.dart | 3 - lib/dart/lib/src/protocol/t_map.dart | 3 - lib/dart/lib/src/protocol/t_message.dart | 6 +- .../src/protocol/t_multiplexed_protocol.dart | 6 +- lib/dart/lib/src/protocol/t_protocol.dart | 1 - .../src/protocol/t_protocol_decorator.dart | 11 +++- .../lib/src/protocol/t_protocol_error.dart | 4 -- .../lib/src/protocol/t_protocol_util.dart | 3 - lib/dart/lib/src/protocol/t_set.dart | 3 - lib/dart/lib/src/protocol/t_struct.dart | 5 +- lib/dart/lib/src/protocol/t_type.dart | 22 ++++--- lib/dart/lib/src/t_application_error.dart | 17 +++--- lib/dart/lib/src/t_base.dart | 3 - lib/dart/lib/src/t_error.dart | 2 - .../src/transport/t_buffered_transport.dart | 6 +- .../lib/src/transport/t_framed_transport.dart | 5 +- .../lib/src/transport/t_http_transport.dart | 8 +-- lib/dart/lib/src/transport/t_socket.dart | 8 +-- .../lib/src/transport/t_socket_transport.dart | 2 - lib/dart/lib/src/transport/t_transport.dart | 8 +-- .../lib/src/transport/t_transport_error.dart | 5 -- 25 files changed, 84 insertions(+), 135 deletions(-) diff --git a/lib/dart/lib/src/html/t_web_socket.dart b/lib/dart/lib/src/html/t_web_socket.dart index 375d71e70c0..2753161f04d 100644 --- a/lib/dart/lib/src/html/t_web_socket.dart +++ b/lib/dart/lib/src/html/t_web_socket.dart @@ -26,10 +26,8 @@ import 'dart:html' show WebSocket; import 'package:thrift/thrift.dart'; - /// A [TSocket] backed by a [WebSocket] from dart:html class TWebSocket implements TSocket { - static const utf8Codec = const Utf8Codec(); final Uri url; @@ -43,9 +41,9 @@ class TWebSocket implements TSocket { final List>> _completers = []; final List<_Request> _requests = []; - TWebSocket(this.url) : - _onStateController = new StreamController.broadcast(), - _onErrorController = new StreamController.broadcast() { + TWebSocket(this.url) + : _onStateController = new StreamController.broadcast(), + _onErrorController = new StreamController.broadcast() { if (url == null || !url.hasAuthority || !url.hasPort) { throw new ArgumentError("Invalid url"); } @@ -55,12 +53,13 @@ class TWebSocket implements TSocket { bool get isOpen => _socket != null && _socket.readyState == WebSocket.OPEN; - bool get isClosed => _socket == null || _socket.readyState == WebSocket.CLOSED; + bool get isClosed => + _socket == null || _socket.readyState == WebSocket.CLOSED; void open() { if (!isClosed) { - throw new TTransportError(TTransportErrorType.ALREADY_OPEN, - "Socket already connected"); + throw new TTransportError( + TTransportErrorType.ALREADY_OPEN, "Socket already connected"); } _socket = new WebSocket(url.toString()); diff --git a/lib/dart/lib/src/protocol/t_binary_protocol.dart b/lib/dart/lib/src/protocol/t_binary_protocol.dart index 2d1aa837a10..91db36acadb 100644 --- a/lib/dart/lib/src/protocol/t_binary_protocol.dart +++ b/lib/dart/lib/src/protocol/t_binary_protocol.dart @@ -21,7 +21,6 @@ part of thrift; /// /// Adapted from the C# version. class TBinaryProtocol extends TProtocol { - static const int VERSION_MASK = 0xffff0000; static const int VERSION_1 = 0x80010000; @@ -30,8 +29,9 @@ class TBinaryProtocol extends TProtocol { final bool strictRead; final bool strictWrite; - - TBinaryProtocol(TTransport transport, {this.strictRead: false, this.strictWrite: true}) : super(transport); + TBinaryProtocol(TTransport transport, + {this.strictRead: false, this.strictWrite: true}) + : super(transport); /// write void writeMessageBegin(TMessage message) { @@ -141,14 +141,16 @@ class TBinaryProtocol extends TProtocol { if (size < 0) { int version = size & VERSION_MASK; if (version != VERSION_1) { - throw new TProtocolError(TProtocolErrorType.BAD_VERSION, "Bad version in readMessageBegin: $version"); + throw new TProtocolError(TProtocolErrorType.BAD_VERSION, + "Bad version in readMessageBegin: $version"); } type = size & 0x000000ff; name = readString(); seqid = readI32(); } else { if (strictRead) { - throw new TProtocolError(TProtocolErrorType.BAD_VERSION, "Missing version in readMessageBegin"); + throw new TProtocolError(TProtocolErrorType.BAD_VERSION, + "Missing version in readMessageBegin"); } name = _readString(size); type = readByte(); @@ -252,5 +254,4 @@ class TBinaryProtocol extends TProtocol { transport.readAll(binaryIn, 0, size); return binaryIn.buffer.asByteData(); } - } diff --git a/lib/dart/lib/src/protocol/t_field.dart b/lib/dart/lib/src/protocol/t_field.dart index bbf323ac4ac..444b4e57a9c 100644 --- a/lib/dart/lib/src/protocol/t_field.dart +++ b/lib/dart/lib/src/protocol/t_field.dart @@ -18,11 +18,9 @@ part of thrift; class TField { - final String name; final int type; final int id; TField(this.name, this.type, this.id); - } diff --git a/lib/dart/lib/src/protocol/t_json_protocol.dart b/lib/dart/lib/src/protocol/t_json_protocol.dart index a49d3e3bd8d..73283acccef 100644 --- a/lib/dart/lib/src/protocol/t_json_protocol.dart +++ b/lib/dart/lib/src/protocol/t_json_protocol.dart @@ -21,7 +21,6 @@ part of thrift; /// /// Adapted from the C# version. class TJsonProtocol extends TProtocol { - static const int VERSION_1 = 1; static const Utf8Codec utf8Codec = const Utf8Codec(); @@ -61,7 +60,7 @@ class TJsonProtocol extends TProtocol { byte <= _Constants.HEX_9.codeUnitAt(0)) { return byte - _Constants.HEX_0.codeUnitAt(0); } else if (byte >= _Constants.HEX_A.codeUnitAt(0) && - byte <= _Constants.HEX_F.codeUnitAt(0)) { + byte <= _Constants.HEX_F.codeUnitAt(0)) { byte += 10; return byte - _Constants.HEX_A.codeUnitAt(0); } else { @@ -268,7 +267,6 @@ class TJsonProtocol extends TProtocol { _writeJsonBase64(bytes.buffer.asUint8List()); } - /// read List _readJsonString({bool skipContext: false}) { @@ -342,7 +340,7 @@ class TJsonProtocol extends TProtocol { try { return int.parse(str); - } on FormatException catch(_) { + } on FormatException catch (_) { throw new TProtocolError(TProtocolErrorType.INVALID_DATA, "Bad data encounted in numeric data"); } @@ -355,11 +353,11 @@ class TJsonProtocol extends TProtocol { List bytes = _readJsonString(skipContext: true); double d = double.parse(utf8Codec.decode(bytes), (_) { throw new TProtocolError(TProtocolErrorType.INVALID_DATA, - "Bad data encounted in numeric data"); + "Bad data encounted in numeric data"); }); if (!_context.escapeNumbers && !d.isNaN && !d.isInfinite) { throw new TProtocolError(TProtocolErrorType.INVALID_DATA, - "Numeric data unexpectedly quoted"); + "Numeric data unexpectedly quoted"); } return d; } else { @@ -369,7 +367,7 @@ class TJsonProtocol extends TProtocol { } return double.parse(_readJsonNumericChars(), (_) { throw new TProtocolError(TProtocolErrorType.INVALID_DATA, - "Bad data encounted in numeric data"); + "Bad data encounted in numeric data"); }); } } @@ -405,8 +403,8 @@ class TJsonProtocol extends TProtocol { TMessage readMessageBegin() { _readJsonArrayStart(); if (_readJsonInteger() != VERSION_1) { - throw new TProtocolError(TProtocolErrorType.BAD_VERSION, - "Message contained bad version."); + throw new TProtocolError( + TProtocolErrorType.BAD_VERSION, "Message contained bad version."); } List buffer = _readJsonString(); @@ -519,11 +517,9 @@ class TJsonProtocol extends TProtocol { ByteData readBinary() { return new Uint8List.fromList(_readJsonBase64()).buffer.asByteData(); } - } class _Constants { - static const utf8codec = const Utf8Codec(); static const String HEX_0 = '0'; @@ -542,12 +538,13 @@ class _Constants { static const String ESCSEQ = r'\u00'; static final List JSON_CHAR_TABLE = new List.unmodifiable([ - 0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes - 'b'.codeUnitAt(0), 't'.codeUnitAt(0), 'n'.codeUnitAt(0), 0, 'f'.codeUnitAt(0), 'r'.codeUnitAt(0), 0, 0, // 8 bytes - 0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes - 0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes - 1, 1, '"'.codeUnitAt(0), 1, 1, 1, 1, 1, // 8 bytes - 1, 1, 1, 1, 1, 1, 1, 1 // 8 bytes + 0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes + 'b'.codeUnitAt(0), 't'.codeUnitAt(0), 'n'.codeUnitAt(0), 0, // 4 bytes + 'f'.codeUnitAt(0), 'r'.codeUnitAt(0), 0, 0, // 4 bytes + 0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes + 0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes + 1, 1, '"'.codeUnitAt(0), 1, 1, 1, 1, 1, // 8 bytes + 1, 1, 1, 1, 1, 1, 1, 1 // 8 bytes ]); static const String ESCAPE_CHARS = r'"\/bfnrt'; @@ -613,17 +610,29 @@ class _Constants { } static final Set _JSON_NUMERICS = new Set.from([ - '+', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'E', 'e' - ].map((String s) => s.codeUnitAt(0))); + '+'.codeUnitAt(0), + '-'.codeUnitAt(0), + '.'.codeUnitAt(0), + '0'.codeUnitAt(0), + '1'.codeUnitAt(0), + '2'.codeUnitAt(0), + '3'.codeUnitAt(0), + '4'.codeUnitAt(0), + '5'.codeUnitAt(0), + '6'.codeUnitAt(0), + '7'.codeUnitAt(0), + '8'.codeUnitAt(0), + '9'.codeUnitAt(0), + 'E'.codeUnitAt(0), + 'e'.codeUnitAt(0) + ]); static bool isJsonNumeric(int byte) { return _JSON_NUMERICS.contains(byte); } - } class _LookaheadReader { - final TJsonProtocol protocol; _LookaheadReader(this.protocol); @@ -649,11 +658,9 @@ class _LookaheadReader { return _data[0]; } - } class _BaseContext { - final TJsonProtocol protocol; _BaseContext(this.protocol); @@ -663,11 +670,9 @@ class _BaseContext { void read() {} bool get escapeNumbers => false; - } class _ListContext extends _BaseContext { - _ListContext(TJsonProtocol protocol) : super(protocol); bool _first = true; @@ -687,11 +692,9 @@ class _ListContext extends _BaseContext { protocol.readJsonSyntaxChar(_Constants.COMMA); } } - } class _PairContext extends _BaseContext { - _PairContext(TJsonProtocol protocol) : super(protocol); bool _first = true; @@ -720,5 +723,4 @@ class _PairContext extends _BaseContext { } bool get escapeNumbers => _colon; - } diff --git a/lib/dart/lib/src/protocol/t_list.dart b/lib/dart/lib/src/protocol/t_list.dart index 10429bcec70..49f467329f2 100644 --- a/lib/dart/lib/src/protocol/t_list.dart +++ b/lib/dart/lib/src/protocol/t_list.dart @@ -17,12 +17,9 @@ part of thrift; - class TList { - final int elementType; final int length; TList(this.elementType, this.length); - } diff --git a/lib/dart/lib/src/protocol/t_map.dart b/lib/dart/lib/src/protocol/t_map.dart index e448ccaaad2..efdf6813a2d 100644 --- a/lib/dart/lib/src/protocol/t_map.dart +++ b/lib/dart/lib/src/protocol/t_map.dart @@ -17,13 +17,10 @@ part of thrift; - class TMap { - final int keyType; final int valueType; final int length; TMap(this.keyType, this.valueType, this.length); - } diff --git a/lib/dart/lib/src/protocol/t_message.dart b/lib/dart/lib/src/protocol/t_message.dart index 5c31f06cbc7..cc7b8868005 100644 --- a/lib/dart/lib/src/protocol/t_message.dart +++ b/lib/dart/lib/src/protocol/t_message.dart @@ -18,16 +18,13 @@ part of thrift; class TMessageType { - - static const int CALL = 1; + static const int CALL = 1; static const int REPLY = 2; static const int EXCEPTION = 3; static const int ONEWAY = 4; - } class TMessage { - final String name; final int type; final int seqid; @@ -35,5 +32,4 @@ class TMessage { TMessage(this.name, this.type, this.seqid); String toString() => ""; - } diff --git a/lib/dart/lib/src/protocol/t_multiplexed_protocol.dart b/lib/dart/lib/src/protocol/t_multiplexed_protocol.dart index 7a27a65b831..078a6d72e3b 100644 --- a/lib/dart/lib/src/protocol/t_multiplexed_protocol.dart +++ b/lib/dart/lib/src/protocol/t_multiplexed_protocol.dart @@ -19,19 +19,18 @@ part of thrift; /// Adapted from the C# version. class TMultiplexedProtocol extends TProtocolDecorator { - static const SEPARATOR = ':'; final String _serviceName; TMultiplexedProtocol(TProtocol protocol, String serviceName) - : _serviceName = serviceName, super(protocol) { + : _serviceName = serviceName, + super(protocol) { if (serviceName == null) { throw new ArgumentError.notNull("serviceName"); } } - void writeMessageBegin(TMessage message) { if (message.type == TMessageType.CALL || message.type == TMessageType.ONEWAY) { @@ -41,5 +40,4 @@ class TMultiplexedProtocol extends TProtocolDecorator { super.writeMessageBegin(message); } - } diff --git a/lib/dart/lib/src/protocol/t_protocol.dart b/lib/dart/lib/src/protocol/t_protocol.dart index b8639846467..d2023f09b81 100644 --- a/lib/dart/lib/src/protocol/t_protocol.dart +++ b/lib/dart/lib/src/protocol/t_protocol.dart @@ -18,7 +18,6 @@ part of thrift; abstract class TProtocol { - final TTransport transport; TProtocol(this.transport); diff --git a/lib/dart/lib/src/protocol/t_protocol_decorator.dart b/lib/dart/lib/src/protocol/t_protocol_decorator.dart index eee2e83ad92..457ccec161e 100644 --- a/lib/dart/lib/src/protocol/t_protocol_decorator.dart +++ b/lib/dart/lib/src/protocol/t_protocol_decorator.dart @@ -21,17 +21,18 @@ part of thrift; /// /// Adapted from the C# version. class TProtocolDecorator extends TProtocol { - final TProtocol _protocol; TProtocolDecorator(TProtocol protocol) - : _protocol = protocol, super(protocol.transport); + : _protocol = protocol, + super(protocol.transport); /// Write void writeMessageBegin(TMessage message) { _protocol.writeMessageBegin(message); } + void writeMessageEnd() { _protocol.writeMessageEnd(); } @@ -39,6 +40,7 @@ class TProtocolDecorator extends TProtocol { void writeStructBegin(TStruct struct) { _protocol.writeStructBegin(struct); } + void writeStructEnd() { _protocol.writeStructEnd(); } @@ -46,9 +48,11 @@ class TProtocolDecorator extends TProtocol { void writeFieldBegin(TField field) { _protocol.writeFieldBegin(field); } + void writeFieldEnd() { _protocol.writeFieldEnd(); } + void writeFieldStop() { _protocol.writeFieldStop(); } @@ -56,6 +60,7 @@ class TProtocolDecorator extends TProtocol { void writeMapBegin(TMap map) { _protocol.writeMapBegin(map); } + void writeMapEnd() { _protocol.writeMapEnd(); } @@ -63,6 +68,7 @@ class TProtocolDecorator extends TProtocol { void writeListBegin(TList list) { _protocol.writeListBegin(list); } + void writeListEnd() { _protocol.writeListEnd(); } @@ -70,6 +76,7 @@ class TProtocolDecorator extends TProtocol { void writeSetBegin(TSet set) { _protocol.writeSetBegin(set); } + void writeSetEnd() { _protocol.writeSetEnd(); } diff --git a/lib/dart/lib/src/protocol/t_protocol_error.dart b/lib/dart/lib/src/protocol/t_protocol_error.dart index 54a7ca2b50b..456baeb798e 100644 --- a/lib/dart/lib/src/protocol/t_protocol_error.dart +++ b/lib/dart/lib/src/protocol/t_protocol_error.dart @@ -18,7 +18,6 @@ part of thrift; class TProtocolErrorType { - static const int UNKNOWN = 0; static const int INVALID_DATA = 1; static const int NEGATIVE_SIZE = 2; @@ -26,12 +25,9 @@ class TProtocolErrorType { static const int BAD_VERSION = 4; static const int NOT_IMPLEMENTED = 5; static const int DEPTH_LIMIT = 6; - } class TProtocolError extends TError { - TProtocolError([int type = TProtocolErrorType.UNKNOWN, String message = ""]) : super(type, message); - } diff --git a/lib/dart/lib/src/protocol/t_protocol_util.dart b/lib/dart/lib/src/protocol/t_protocol_util.dart index 409250136c6..ad20068c201 100644 --- a/lib/dart/lib/src/protocol/t_protocol_util.dart +++ b/lib/dart/lib/src/protocol/t_protocol_util.dart @@ -17,9 +17,7 @@ part of thrift; - class TProtocolUtil { - // equal to JavaScript Number.MAX_SAFE_INTEGER, 2^53 - 1 static const int defaultRecursionLimit = 9007199254740991; @@ -106,5 +104,4 @@ class TProtocolUtil { break; } } - } diff --git a/lib/dart/lib/src/protocol/t_set.dart b/lib/dart/lib/src/protocol/t_set.dart index 585c9a2dbac..b342537e38e 100644 --- a/lib/dart/lib/src/protocol/t_set.dart +++ b/lib/dart/lib/src/protocol/t_set.dart @@ -17,12 +17,9 @@ part of thrift; - class TSet { - final int elementType; final int length; TSet(this.elementType, this.length); - } diff --git a/lib/dart/lib/src/protocol/t_struct.dart b/lib/dart/lib/src/protocol/t_struct.dart index fe0f5fa4b5c..0303f6395a3 100644 --- a/lib/dart/lib/src/protocol/t_struct.dart +++ b/lib/dart/lib/src/protocol/t_struct.dart @@ -17,11 +17,8 @@ part of thrift; - class TStruct { - final String name; - TStruct([this.name=""]); - + TStruct([this.name = ""]); } diff --git a/lib/dart/lib/src/protocol/t_type.dart b/lib/dart/lib/src/protocol/t_type.dart index b6d937054cc..3919d969d76 100644 --- a/lib/dart/lib/src/protocol/t_type.dart +++ b/lib/dart/lib/src/protocol/t_type.dart @@ -18,19 +18,17 @@ part of thrift; class TType { - - static const int STOP = 0; - static const int VOID = 1; - static const int BOOL = 2; - static const int BYTE = 3; + static const int STOP = 0; + static const int VOID = 1; + static const int BOOL = 2; + static const int BYTE = 3; static const int DOUBLE = 4; - static const int I16 = 6; - static const int I32 = 8; - static const int I64 = 10; + static const int I16 = 6; + static const int I32 = 8; + static const int I64 = 10; static const int STRING = 11; static const int STRUCT = 12; - static const int MAP = 13; - static const int SET = 14; - static const int LIST = 15; - + static const int MAP = 13; + static const int SET = 14; + static const int LIST = 15; } diff --git a/lib/dart/lib/src/t_application_error.dart b/lib/dart/lib/src/t_application_error.dart index 5dc6f353ec5..6f8abd4bd10 100644 --- a/lib/dart/lib/src/t_application_error.dart +++ b/lib/dart/lib/src/t_application_error.dart @@ -18,7 +18,6 @@ part of thrift; class TApplicationErrorType { - static const int UNKNOWN = 0; static const int UNKNOWN_METHOD = 1; static const int INVALID_MESSAGE_TYPE = 2; @@ -30,19 +29,19 @@ class TApplicationErrorType { static const int INVALID_TRANSFORM = 8; static const int INVALID_PROTOCOL = 9; static const int UNSUPPORTED_CLIENT_TYPE = 10; - } class TApplicationError extends TError { - static final TStruct _struct = new TStruct("TApplicationError"); static const int MESSAGE = 1; - static final TField _messageField = new TField("message", TType.STRING, MESSAGE); + static final TField _messageField = + new TField("message", TType.STRING, MESSAGE); static const int TYPE = 2; static final TField _typeField = new TField("type", TType.I32, TYPE); - TApplicationError([int type = TApplicationErrorType.UNKNOWN, - String message = ""]) : super(type, message); + TApplicationError( + [int type = TApplicationErrorType.UNKNOWN, String message = ""]) + : super(type, message); static TApplicationError read(TProtocol iprot) { TField field; @@ -62,8 +61,7 @@ class TApplicationError extends TError { case MESSAGE: if (field.type == TType.STRING) { message = iprot.readString(); - } - else { + } else { TProtocolUtil.skip(iprot, field.type); } break; @@ -71,8 +69,7 @@ class TApplicationError extends TError { case TYPE: if (field.type == TType.I32) { type = iprot.readI32(); - } - else { + } else { TProtocolUtil.skip(iprot, field.type); } break; diff --git a/lib/dart/lib/src/t_base.dart b/lib/dart/lib/src/t_base.dart index 56ce1f58e69..d5571b6dca3 100644 --- a/lib/dart/lib/src/t_base.dart +++ b/lib/dart/lib/src/t_base.dart @@ -17,9 +17,7 @@ part of thrift; - abstract class TBase { - /// Reads the TObject from the given input protocol. void read(TProtocol iprot); @@ -36,5 +34,4 @@ abstract class TBase { /// Set a field's value by [fieldId]. Primitive types must be "boxed" in the /// appropriate object wrapper type. setFieldValue(int fieldId, Object value); - } diff --git a/lib/dart/lib/src/t_error.dart b/lib/dart/lib/src/t_error.dart index 58247695bf0..93ab732392f 100644 --- a/lib/dart/lib/src/t_error.dart +++ b/lib/dart/lib/src/t_error.dart @@ -18,12 +18,10 @@ part of thrift; class TError extends Error { - final String message; final int type; TError(this.type, this.message); String toString() => ""; - } diff --git a/lib/dart/lib/src/transport/t_buffered_transport.dart b/lib/dart/lib/src/transport/t_buffered_transport.dart index 55369f41418..59d230b7122 100644 --- a/lib/dart/lib/src/transport/t_buffered_transport.dart +++ b/lib/dart/lib/src/transport/t_buffered_transport.dart @@ -19,7 +19,6 @@ part of thrift; /// Buffered implementation of [TTransport]. class TBufferedTransport extends TTransport { - final List _writeBuffer = []; Iterator _readIterator; @@ -45,8 +44,8 @@ class TBufferedTransport extends TTransport { void open() { if (isOpen) { - throw new TTransportError(TTransportErrorType.ALREADY_OPEN, - "The transport is already open"); + throw new TTransportError( + TTransportErrorType.ALREADY_OPEN, "The transport is already open"); } _isOpen = true; _reset(); @@ -99,5 +98,4 @@ class TBufferedTransport extends TTransport { Future flush() { return new Future.value(); } - } diff --git a/lib/dart/lib/src/transport/t_framed_transport.dart b/lib/dart/lib/src/transport/t_framed_transport.dart index d7fcb62b627..8e60fc5a2f1 100644 --- a/lib/dart/lib/src/transport/t_framed_transport.dart +++ b/lib/dart/lib/src/transport/t_framed_transport.dart @@ -21,7 +21,6 @@ part of thrift; /// /// Adapted from the Java Framed transport. class TFramedTransport extends TBufferedTransport { - static const I32_LENGTH = 4; final TTransport _transport; @@ -62,8 +61,8 @@ class TFramedTransport extends TBufferedTransport { int size = headerBytes.buffer.asByteData().getUint32(0); if (size < 0) { - throw new TTransportError(TTransportErrorType.UNKNOWN, - "Read a negative frame size: $size"); + throw new TTransportError( + TTransportErrorType.UNKNOWN, "Read a negative frame size: $size"); } List buffer = new List(size); diff --git a/lib/dart/lib/src/transport/t_http_transport.dart b/lib/dart/lib/src/transport/t_http_transport.dart index e4986ada63a..15c96b0fa45 100644 --- a/lib/dart/lib/src/transport/t_http_transport.dart +++ b/lib/dart/lib/src/transport/t_http_transport.dart @@ -29,7 +29,6 @@ part of thrift; /// /// Adapted from the JS XHR HTTP transport. class THttpClientTransport extends TBufferedTransport { - final http.BrowserClient httpClient; final THttpConfig config; @@ -46,15 +45,13 @@ class THttpClientTransport extends TBufferedTransport { Future flush() async { var body = _consumeWriteBuffer(); - var response = await httpClient.post( - config.url, headers: config.headers, body: body); + var response = + await httpClient.post(config.url, headers: config.headers, body: body); _setReadBuffer(response.bodyBytes); } - } class THttpConfig { - final Uri url; Map _headers; @@ -80,5 +77,4 @@ class THttpConfig { _headers = new Map.unmodifiable(h); } - } diff --git a/lib/dart/lib/src/transport/t_socket.dart b/lib/dart/lib/src/transport/t_socket.dart index 4dc16ff614e..163dfc99a27 100644 --- a/lib/dart/lib/src/transport/t_socket.dart +++ b/lib/dart/lib/src/transport/t_socket.dart @@ -17,14 +17,9 @@ part of thrift; - -enum TSocketState { - CLOSED, - OPEN -} +enum TSocketState { CLOSED, OPEN } abstract class TSocket { - Stream get onState; Stream get onError; @@ -38,5 +33,4 @@ abstract class TSocket { void close(); Future> send(List data); - } diff --git a/lib/dart/lib/src/transport/t_socket_transport.dart b/lib/dart/lib/src/transport/t_socket_transport.dart index dcceea59b95..22fbc1cf8e1 100644 --- a/lib/dart/lib/src/transport/t_socket_transport.dart +++ b/lib/dart/lib/src/transport/t_socket_transport.dart @@ -28,7 +28,6 @@ part of thrift; /// /// Adapted from the JS WebSocket transport. class TSocketTransport extends TBufferedTransport { - final TSocket socket; final Logger log = new Logger('thrift.TSocketTransport'); @@ -56,5 +55,4 @@ class TSocketTransport extends TBufferedTransport { List result = await socket.send(_consumeWriteBuffer()); _setReadBuffer(result); } - } diff --git a/lib/dart/lib/src/transport/t_transport.dart b/lib/dart/lib/src/transport/t_transport.dart index 7a75c8a894b..e90269fabb8 100644 --- a/lib/dart/lib/src/transport/t_transport.dart +++ b/lib/dart/lib/src/transport/t_transport.dart @@ -17,9 +17,7 @@ part of thrift; - abstract class TTransport { - /// Queries whether the transport is open. /// Returns [true] if the transport is open. bool get isOpen; @@ -44,9 +42,10 @@ abstract class TTransport { int got = 0; int ret = 0; while (got < length) { - ret = read(buffer, offset+got, length-got); + ret = read(buffer, offset + got, length - got); if (ret <= 0) { - throw new TTransportError(TTransportErrorType.UNKNOWN, + throw new TTransportError( + TTransportErrorType.UNKNOWN, "Cannot read. Remote side has closed. Tried to read $length " "bytes, but only got $got bytes."); } @@ -68,5 +67,4 @@ abstract class TTransport { /// Flush any pending data out of a transport buffer. /// Throws [TTransportError] if there was an error writing out data. Future flush(); - } diff --git a/lib/dart/lib/src/transport/t_transport_error.dart b/lib/dart/lib/src/transport/t_transport_error.dart index 622fc02c974..d3508e05223 100644 --- a/lib/dart/lib/src/transport/t_transport_error.dart +++ b/lib/dart/lib/src/transport/t_transport_error.dart @@ -17,20 +17,15 @@ part of thrift; - class TTransportErrorType { - static const int UNKNOWN = 0; static const int NOT_OPEN = 1; static const int ALREADY_OPEN = 2; static const int TIMED_OUT = 3; static const int END_OF_FILE = 4; - } class TTransportError extends TError { - TTransportError([int type = TTransportErrorType.UNKNOWN, String message = ""]) : super(type, message); - } From c410c6d85806489a17f844fab853461104e94ab6 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Fri, 4 Sep 2015 22:11:31 -0500 Subject: [PATCH 11/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Optimizations when using List.length since the base implementation iterates over elements in the getter. --- lib/dart/lib/src/html/t_web_socket.dart | 2 +- lib/dart/lib/src/protocol/t_json_protocol.dart | 3 ++- lib/dart/lib/src/transport/t_framed_transport.dart | 13 +++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/dart/lib/src/html/t_web_socket.dart b/lib/dart/lib/src/html/t_web_socket.dart index 2753161f04d..0f569d5e486 100644 --- a/lib/dart/lib/src/html/t_web_socket.dart +++ b/lib/dart/lib/src/html/t_web_socket.dart @@ -85,7 +85,7 @@ class TWebSocket implements TSocket { } void _sendRequests() { - while (isOpen && _requests.length > 0) { + while (isOpen && _requests.isNotEmpty) { _Request request = _requests.removeAt(0); _completers.add(request.completer); _socket.sendString(utf8Codec.decode(request.data)); diff --git a/lib/dart/lib/src/protocol/t_json_protocol.dart b/lib/dart/lib/src/protocol/t_json_protocol.dart index 73283acccef..3342321894f 100644 --- a/lib/dart/lib/src/protocol/t_json_protocol.dart +++ b/lib/dart/lib/src/protocol/t_json_protocol.dart @@ -80,7 +80,8 @@ class TJsonProtocol extends TProtocol { _context.write(); transport.writeAll(_Constants.QUOTE.codeUnits); - for (int i = 0; i < bytes.length; i++) { + int length = bytes.length; + for (int i = 0; i < length; i++) { int byte = bytes[i]; if ((byte & 0x00FF) >= 0x30) { if (byte == _Constants.BACKSLASH.codeUnitAt(0)) { diff --git a/lib/dart/lib/src/transport/t_framed_transport.dart b/lib/dart/lib/src/transport/t_framed_transport.dart index 8e60fc5a2f1..58ccc73d2f8 100644 --- a/lib/dart/lib/src/transport/t_framed_transport.dart +++ b/lib/dart/lib/src/transport/t_framed_transport.dart @@ -21,11 +21,11 @@ part of thrift; /// /// Adapted from the Java Framed transport. class TFramedTransport extends TBufferedTransport { - static const I32_LENGTH = 4; + static const uint32ByteCount = 4; final TTransport _transport; - final Uint8List headerBytes = new Uint8List(I32_LENGTH); + final Uint8List headerBytes = new Uint8List(uint32ByteCount); TFramedTransport(TTransport transport) : _transport = transport { if (transport == null) { @@ -57,7 +57,7 @@ class TFramedTransport extends TBufferedTransport { } void _readFrame() { - _transport.readAll(headerBytes, 0, I32_LENGTH); + _transport.readAll(headerBytes, 0, uint32ByteCount); int size = headerBytes.buffer.asByteData().getUint32(0); if (size < 0) { @@ -72,10 +72,11 @@ class TFramedTransport extends TBufferedTransport { Future flush() { List buffer = _consumeWriteBuffer(); + int length = buffer.length; - headerBytes.buffer.asByteData().setUint32(0, buffer.length); - _transport.writeAll(headerBytes); - _transport.writeAll(buffer); + headerBytes.buffer.asByteData().setUint32(0, length); + _transport.write(headerBytes, 0, uint32ByteCount); + _transport.write(buffer, 0, length); return _transport.flush(); } From 6195d227d9357028b0f6da18cf3d77ff692fcfcd Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Sun, 6 Sep 2015 11:57:46 -0500 Subject: [PATCH 12/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Fix BufferedTransport.flush to be able to read from the write buffer. --- lib/dart/lib/src/transport/t_buffered_transport.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/dart/lib/src/transport/t_buffered_transport.dart b/lib/dart/lib/src/transport/t_buffered_transport.dart index 59d230b7122..4fdc8db3fff 100644 --- a/lib/dart/lib/src/transport/t_buffered_transport.dart +++ b/lib/dart/lib/src/transport/t_buffered_transport.dart @@ -96,6 +96,8 @@ class TBufferedTransport extends TTransport { } Future flush() { + _readIterator = _consumeWriteBuffer().iterator; + return new Future.value(); } } From e186fb68fa8c08297283cab0a59dd54c48a8ba9c Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Tue, 8 Sep 2015 10:05:56 -0500 Subject: [PATCH 13/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Fix typo in Dart SDK requirement --- lib/dart/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dart/pubspec.yaml b/lib/dart/pubspec.yaml index d38d703067c..d59fd625af4 100644 --- a/lib/dart/pubspec.yaml +++ b/lib/dart/pubspec.yaml @@ -12,4 +12,4 @@ dependencies: dev_dependencies: test: '>=0.12.0' environment: - sdk: '>=0.12.0' + sdk: '>=1.12.0' From 017cbd7c4fb2436513db4a4bf5e2c65a82fb973f Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Tue, 8 Sep 2015 10:52:56 -0500 Subject: [PATCH 14/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Add pubspec.yaml to generated files. --- compiler/cpp/src/generate/t_dart_generator.cc | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/compiler/cpp/src/generate/t_dart_generator.cc b/compiler/cpp/src/generate/t_dart_generator.cc index 5d8a2bfc81c..35f66cabcd3 100644 --- a/compiler/cpp/src/generate/t_dart_generator.cc +++ b/compiler/cpp/src/generate/t_dart_generator.cc @@ -40,6 +40,9 @@ using std::vector; static const string endl = "\n"; // avoid ostream << std::endl flushes static const string endl2 = "\n\n"; +/* Should reflect the current version in lib/dart/pubspec.yaml */ +static const string dart_thrift_version = "0.1.0"; + /* forward declarations */ string initial_caps_to_underscores(string name); @@ -112,6 +115,7 @@ class t_dart_generator : public t_oop_generator { void export_class_to_library(string file_name, string class_name); void generate_dart_library(); + void generate_dart_pubspec(); void generate_consts(std::vector consts); @@ -317,12 +321,13 @@ string t_dart_generator::dart_thrift_imports() { */ void t_dart_generator::close_generator() { generate_dart_library(); + generate_dart_pubspec(); } void t_dart_generator::generate_dart_library() { - string f_library_name_ = base_dir_ + "/lib/" + library_name_ + ".dart"; + string f_library_name = base_dir_ + "/lib/" + library_name_ + ".dart"; ofstream f_library; - f_library.open(f_library_name_.c_str()); + f_library.open(f_library_name.c_str()); f_library << autogen_comment() << endl; f_library << "library " << library_name_ << ";" << endl2; @@ -335,6 +340,35 @@ void t_dart_generator::export_class_to_library(string file_name, string class_na library_exports_ += "export 'src/" + file_name + ".dart' show " + class_name + ";" + endl; } +void t_dart_generator::generate_dart_pubspec() { + string f_pubspec_name = base_dir_ + "/pubspec.yaml"; + ofstream f_pubspec; + f_pubspec.open(f_pubspec_name.c_str()); + + indent(f_pubspec) << "name: " << library_name_ << endl; + indent(f_pubspec) << "version: 0.0.1" << endl; + indent(f_pubspec) << "description: Autogenerated by Thrift Compiler" << endl; + f_pubspec << endl; + + indent(f_pubspec) << "environment:" << endl; + indent_up(); + indent(f_pubspec) << "sdk: '>=1.12.0'" << endl; + indent_down(); + f_pubspec << endl; + + indent(f_pubspec) << "dependencies:" << endl; + indent_up(); + indent(f_pubspec) << "logging: '>=0.9.0 <0.12.0'" << endl; + indent(f_pubspec) << "thrift: # '>=" << dart_thrift_version << "'" << endl; + indent_up(); + indent(f_pubspec) << "path: ../../lib/dart" << endl; + indent_down(); + indent_down(); + f_pubspec << endl; + + f_pubspec.close(); +} + /** * Not used * From e347643f476545f9a7686aaafe89c2b967e1fd3b Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Tue, 8 Sep 2015 15:07:43 -0500 Subject: [PATCH 15/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Fix generation problems exposed by tutorial specification, related to inheritance and constants. --- compiler/cpp/src/generate/t_dart_generator.cc | 136 +++++++++++------- 1 file changed, 86 insertions(+), 50 deletions(-) diff --git a/compiler/cpp/src/generate/t_dart_generator.cc b/compiler/cpp/src/generate/t_dart_generator.cc index 35f66cabcd3..0459a865476 100644 --- a/compiler/cpp/src/generate/t_dart_generator.cc +++ b/compiler/cpp/src/generate/t_dart_generator.cc @@ -263,7 +263,7 @@ void t_dart_generator::init_generator() { library_name_ = program_->get_namespace("dart"); } if (library_name_.empty()) { - library_name_ = "my_library"; + library_name_ = program_->get_name(); } library_name_ = replace_all(library_name_, ".", "_"); @@ -310,10 +310,18 @@ string t_dart_generator::service_imports() { * @return List of imports necessary for thrift */ string t_dart_generator::dart_thrift_imports() { - return string() + - "import 'dart:typed_data' show ByteData;" + endl + + string imports = "import 'dart:typed_data' show ByteData;" + endl + "import 'package:thrift/thrift.dart';" + endl + "import 'package:" + library_name_ + "/" + library_name_ + ".dart';" + endl; + + // add imports for included thrift files + const vector& includes = program_->get_includes(); + for (size_t i = 0; i < includes.size(); ++i) { + string include_name = includes[i]->get_namespace("dart"); + imports += "import 'package:" + include_name + "/" + include_name + ".dart';" + endl; + } + + return imports; } /** @@ -361,8 +369,19 @@ void t_dart_generator::generate_dart_pubspec() { indent(f_pubspec) << "logging: '>=0.9.0 <0.12.0'" << endl; indent(f_pubspec) << "thrift: # '>=" << dart_thrift_version << "'" << endl; indent_up(); - indent(f_pubspec) << "path: ../../lib/dart" << endl; + indent(f_pubspec) << "path: ../../../../lib/dart" << endl; indent_down(); + + // add included thrift files as dependencies + const vector& includes = program_->get_includes(); + for (size_t i = 0; i < includes.size(); ++i) { + string include_name = includes[i]->get_namespace("dart"); + indent(f_pubspec) << include_name << ":" << endl; + indent_up(); + indent(f_pubspec) << "path: ../" << include_name << endl; + indent_down(); + } + indent_down(); f_pubspec << endl; @@ -446,7 +465,7 @@ void t_dart_generator::generate_consts(std::vector consts) { return; } - string class_name = program_name_ + "Constants"; + string class_name = get_cap_name(program_name_) + "Constants"; string file_name = get_file_name(class_name); string f_consts_name = src_dir_ + "/" + file_name + ".dart"; @@ -489,14 +508,14 @@ void t_dart_generator::print_const_value(std::ofstream& out, } if (type->is_base_type()) { if (!defval) { - out << type_name(type); + out << type_name(type) << " "; } string v2 = render_const_value(out, name, type, value); out << name; out << " = " << v2 << ";" << endl << endl; } else if (type->is_enum()) { if (!defval) { - out << type_name(type); + out << type_name(type) << " "; } out << name; out << " = " << value->get_integer() << ";" << endl << endl; @@ -505,7 +524,7 @@ void t_dart_generator::print_const_value(std::ofstream& out, vector::const_iterator f_iter; const map& val = value->get_map(); map::const_iterator v_iter; - out << type_name(type) << name << " = new " << type_name(type, false, true) << "();" + out << type_name(type) << " " << name << " = new " << type_name(type, false, true) << "();" << endl; for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { t_type* field_type = NULL; @@ -524,39 +543,53 @@ void t_dart_generator::print_const_value(std::ofstream& out, out << endl; } else if (type->is_map()) { if (!defval) { - out << type_name(type); + out << type_name(type) << " "; } - out << name; - out << " = new " << type_name(type, false, true) << "();" << endl; + out << name << " ="; + scope_up(out); + t_type* ktype = ((t_map*)type)->get_key_type(); t_type* vtype = ((t_map*)type)->get_val_type(); const map& val = value->get_map(); map::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { string key = render_const_value(out, name, ktype, v_iter->first); string val = render_const_value(out, name, vtype, v_iter->second); - indent(out) << name << "[" << key << "] = " << val << ";" << endl; + indent(out) << key << ": " << val << "," << endl; } + scope_down(out, ";" + endl); + out << endl; } else if (type->is_list() || type->is_set()) { if (!defval) { - out << type_name(type); + out << type_name(type) << " "; } - out << name; - out << " = new " << type_name(type, false, true) << "();" << endl; + out << name << " = "; t_type* etype; if (type->is_list()) { + out << "[" << endl; etype = ((t_list*)type)->get_elem_type(); } else { + out << "new " << type_name(type, false, true) << ".from([" << endl; etype = ((t_set*)type)->get_elem_type(); } const vector& val = value->get_list(); vector::const_iterator v_iter; + + indent_up(); for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { string val = render_const_value(out, name, etype, *v_iter); - indent(out) << name << "." << (type->is_list() ? "push" : "add") << "(" << val << ");" - << endl; + indent(out) << val << "," << endl; } + indent_down(); + + if (type->is_list()) { + indent(out) << "];" << endl; + } else { + indent(out) << "]);" << endl; + } + } else { throw "compiler error: no const of type " + type->get_name(); } @@ -1280,12 +1313,6 @@ void t_dart_generator::generate_service(t_service* tservice) { f_service_ << autogen_comment() << dart_library(file_name) << endl; f_service_ << service_imports() << dart_thrift_imports() << endl; - - if (tservice->get_extends() != NULL) { - t_type* parent = tservice->get_extends(); - f_service_ << "import '" << get_file_name(parent->get_name()) << ".dart';" << endl; - } - f_service_ << endl; generate_service_interface(tservice); @@ -1360,29 +1387,32 @@ void t_dart_generator::generate_service_client(t_service* tservice) { string class_name = service_name_ + "Client"; export_class_to_library(get_file_name(service_name_), class_name); indent(f_service_) << "class " << class_name << extends_client - << " extends " << service_name_; + << " implements " << service_name_; scope_up(f_service_); f_service_ << endl; indent(f_service_) << class_name << "(TProtocol iprot, [TProtocol oprot = null])"; - scope_up(f_service_); - if (extends.empty()) { + if (!extends.empty()) { + indent_up(); + f_service_ << endl; + indent(f_service_) << ": super(iprot, oprot);" << endl; + indent_down(); + } else { + scope_up(f_service_); indent(f_service_) << "_iprot = iprot;" << endl; indent(f_service_) << "_oprot = (oprot == null) ? iprot : oprot;" << endl; - } else { - indent(f_service_) << "super(iprot, oprot);" << endl; + scope_down(f_service_); } - scope_down(f_service_); f_service_ << endl; if (extends.empty()) { - indent(f_service_) << "TProtocol _iprot;" << endl; - indent(f_service_) << "TProtocol _oprot;" << endl; - indent(f_service_) << "int _seqid;" << endl << endl; - - indent(f_service_) << "TProtocol getInputProtocol() => _iprot;" << endl2; - indent(f_service_) << "TProtocol getOutputProtocol() => _oprot;" << endl2; + indent(f_service_) << "TProtocol _iprot;" << endl2; + indent(f_service_) << "TProtocol get iprot => _iprot;" << endl2; + indent(f_service_) << "TProtocol _oprot;" << endl2; + indent(f_service_) << "TProtocol get oprot => _oprot;" << endl2; + indent(f_service_) << "int _seqid;" << endl2; + indent(f_service_) << "int get seqid => _seqid;" << endl2; } // Generate client method implementations @@ -1403,9 +1433,9 @@ void t_dart_generator::generate_service_client(t_service* tservice) { const vector& fields = arg_struct->get_members(); // Serialize the request - indent(f_service_) << "_oprot.writeMessageBegin(new TMessage(\"" << funname << "\", " + indent(f_service_) << "oprot.writeMessageBegin(new TMessage(\"" << funname << "\", " << ((*f_iter)->is_oneway() ? "TMessageType.ONEWAY" : "TMessageType.CALL") - << ", _seqid));" << endl; + << ", seqid));" << endl; indent(f_service_) << argsname << " args = new " << argsname << "();" << endl; for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { @@ -1413,24 +1443,24 @@ void t_dart_generator::generate_service_client(t_service* tservice) { << (*fld_iter)->get_name() << ";" << endl; } - indent(f_service_) << "args.write(_oprot);" << endl; - indent(f_service_) << "_oprot.writeMessageEnd();" << endl2; + indent(f_service_) << "args.write(oprot);" << endl; + indent(f_service_) << "oprot.writeMessageEnd();" << endl2; - indent(f_service_) << "await _oprot.transport.flush();" << endl2; + indent(f_service_) << "await oprot.transport.flush();" << endl2; if (!(*f_iter)->is_oneway()) { - indent(f_service_) << "TMessage msg = _iprot.readMessageBegin();" << endl; + indent(f_service_) << "TMessage msg = iprot.readMessageBegin();" << endl; indent(f_service_) << "if (msg.type == TMessageType.EXCEPTION)"; scope_up(f_service_); - indent(f_service_) << "TApplicationError error = TApplicationError.read(_iprot);" << endl; - indent(f_service_) << "_iprot.readMessageEnd();" << endl; + indent(f_service_) << "TApplicationError error = TApplicationError.read(iprot);" << endl; + indent(f_service_) << "iprot.readMessageEnd();" << endl; indent(f_service_) << "throw error;" << endl; scope_down(f_service_, endl2); string result_class = get_result_class_name((*f_iter)->get_name()); indent(f_service_) << result_class << " result = new " << result_class << "();" << endl; - indent(f_service_) << "result.read(_iprot);" << endl; - indent(f_service_) << "_iprot.readMessageEnd();" << endl; + indent(f_service_) << "result.read(iprot);" << endl; + indent(f_service_) << "iprot.readMessageEnd();" << endl; // Careful, only return _result if not a void function if (!(*f_iter)->get_returntype()->is_void()) { @@ -1494,11 +1524,17 @@ void t_dart_generator::generate_service_server(t_service* tservice) { scope_up(f_service_); indent(f_service_) << class_name << "(" << service_name_ << " iface)"; - scope_up(f_service_); if (!extends.empty()) { - indent(f_service_) << "super(iface);" << endl; + indent_up(); + f_service_ << endl; + indent(f_service_) << ": super(iface)"; + indent_down(); + } + scope_up(f_service_); + + if (extends.empty()) { + indent(f_service_) << "iface_ = iface;" << endl; } - indent(f_service_) << "iface_ = iface;" << endl; for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { indent(f_service_) << "PROCESS_MAP[\"" << (*f_iter)->get_name() @@ -2330,6 +2366,6 @@ std::string t_dart_generator::get_enum_class_name(t_type* type) { THRIFT_REGISTER_GENERATOR( dart, "Dart", - " library_name=thrift Optional override for library name.\n" - " gen_server Generate service server classes.\n" + " library_name=my_library Optional override for library name.\n" + " gen_server Generate server service implementations.\n" ); From 21b97b2c76f1ff4ba973502a9e4cbf44d5cdf82b Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Tue, 8 Sep 2015 16:59:18 -0500 Subject: [PATCH 16/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Initialize seqid and increment when client writes message begin. --- compiler/cpp/src/generate/t_dart_generator.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/cpp/src/generate/t_dart_generator.cc b/compiler/cpp/src/generate/t_dart_generator.cc index 0459a865476..0a5ee92e1d4 100644 --- a/compiler/cpp/src/generate/t_dart_generator.cc +++ b/compiler/cpp/src/generate/t_dart_generator.cc @@ -1411,8 +1411,9 @@ void t_dart_generator::generate_service_client(t_service* tservice) { indent(f_service_) << "TProtocol get iprot => _iprot;" << endl2; indent(f_service_) << "TProtocol _oprot;" << endl2; indent(f_service_) << "TProtocol get oprot => _oprot;" << endl2; - indent(f_service_) << "int _seqid;" << endl2; + indent(f_service_) << "int _seqid = 0;" << endl2; indent(f_service_) << "int get seqid => _seqid;" << endl2; + indent(f_service_) << "int nextSeqid() => ++_seqid;" << endl2; } // Generate client method implementations @@ -1435,7 +1436,7 @@ void t_dart_generator::generate_service_client(t_service* tservice) { // Serialize the request indent(f_service_) << "oprot.writeMessageBegin(new TMessage(\"" << funname << "\", " << ((*f_iter)->is_oneway() ? "TMessageType.ONEWAY" : "TMessageType.CALL") - << ", seqid));" << endl; + << ", nextSeqid()));" << endl; indent(f_service_) << argsname << " args = new " << argsname << "();" << endl; for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { From dbf2878d7b89c3fb60e10f6a739e68b7ea8c25c9 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Thu, 10 Sep 2015 11:11:19 -0500 Subject: [PATCH 17/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 In progress. Initial stab at tutorial implementation, with known issues. Changed socket interface for open / close to be async. Added support for sockets that are listening for messages (server). --- compiler/cpp/src/generate/t_dart_generator.cc | 19 +-- .../src/{html => browser}/t_web_socket.dart | 14 ++- lib/dart/lib/src/console/t_web_socket.dart | 110 ++++++++++++++++++ .../src/transport/t_buffered_transport.dart | 17 +-- .../lib/src/transport/t_framed_transport.dart | 12 +- .../lib/src/transport/t_http_transport.dart | 6 +- lib/dart/lib/src/transport/t_socket.dart | 6 +- .../lib/src/transport/t_socket_transport.dart | 37 ++++-- lib/dart/lib/src/transport/t_transport.dart | 4 +- lib/dart/lib/thrift.dart | 19 ++- lib/dart/lib/thrift_browser.dart | 22 ++++ lib/dart/lib/thrift_console.dart | 22 ++++ lib/dart/lib/thrift_html.dart | 3 - tutorial/dart/.gitignore | 4 + tutorial/dart/build.sh | 7 ++ tutorial/dart/client/pubspec.yaml | 17 +++ tutorial/dart/client/web/client.dart | 104 +++++++++++++++++ tutorial/dart/client/web/index.html | 18 +++ tutorial/dart/client/web/styles.css | 9 ++ tutorial/dart/server/bin/main.dart | 87 ++++++++++++++ tutorial/dart/server/pubspec.yaml | 16 +++ tutorial/shared.thrift | 1 + tutorial/tutorial.thrift | 1 + 23 files changed, 498 insertions(+), 57 deletions(-) rename lib/dart/lib/src/{html => browser}/t_web_socket.dart (90%) create mode 100644 lib/dart/lib/src/console/t_web_socket.dart create mode 100644 lib/dart/lib/thrift_browser.dart create mode 100644 lib/dart/lib/thrift_console.dart delete mode 100644 lib/dart/lib/thrift_html.dart create mode 100644 tutorial/dart/.gitignore create mode 100755 tutorial/dart/build.sh create mode 100644 tutorial/dart/client/pubspec.yaml create mode 100644 tutorial/dart/client/web/client.dart create mode 100644 tutorial/dart/client/web/index.html create mode 100644 tutorial/dart/client/web/styles.css create mode 100644 tutorial/dart/server/bin/main.dart create mode 100644 tutorial/dart/server/pubspec.yaml diff --git a/compiler/cpp/src/generate/t_dart_generator.cc b/compiler/cpp/src/generate/t_dart_generator.cc index 0a5ee92e1d4..42f933c2838 100644 --- a/compiler/cpp/src/generate/t_dart_generator.cc +++ b/compiler/cpp/src/generate/t_dart_generator.cc @@ -66,13 +66,6 @@ class t_dart_generator : public t_oop_generator { library_name_ = ""; } - iter = parsed_options.find("gen_server"); - if (iter != parsed_options.end()) { - gen_server_ = true; - } else { - gen_server_ = false; - } - out_dir_base_ = "gen-dart"; } @@ -242,7 +235,6 @@ class t_dart_generator : public t_oop_generator { private: std::ofstream f_service_; - bool gen_server_; std::string library_name_; std::string base_dir_; @@ -1317,11 +1309,7 @@ void t_dart_generator::generate_service(t_service* tservice) { generate_service_interface(tservice); generate_service_client(tservice); - - if (gen_server_) { - generate_service_server(tservice); - } - + generate_service_server(tservice); generate_service_helpers(tservice); f_service_.close(); @@ -2319,7 +2307,7 @@ string t_dart_generator::constant_name(string name) { */ void t_dart_generator::generate_dart_doc(ofstream& out, t_doc* tdoc) { if (tdoc->has_doc()) { - generate_docstring_comment(out, "///\n", "///", tdoc->get_doc(), "///\n"); + generate_docstring_comment(out, "", "/// ", tdoc->get_doc(), ""); } } @@ -2340,7 +2328,7 @@ void t_dart_generator::generate_dart_doc(ofstream& out, t_function* tfunction) { ss << " " << p->get_doc(); } } - generate_docstring_comment(out, "///\n", "///", ss.str(), "///\n"); + generate_docstring_comment(out, "", "/// ", ss.str(), ""); } } @@ -2368,5 +2356,4 @@ THRIFT_REGISTER_GENERATOR( dart, "Dart", " library_name=my_library Optional override for library name.\n" - " gen_server Generate server service implementations.\n" ); diff --git a/lib/dart/lib/src/html/t_web_socket.dart b/lib/dart/lib/src/browser/t_web_socket.dart similarity index 90% rename from lib/dart/lib/src/html/t_web_socket.dart rename to lib/dart/lib/src/browser/t_web_socket.dart index 0f569d5e486..3810054edd7 100644 --- a/lib/dart/lib/src/html/t_web_socket.dart +++ b/lib/dart/lib/src/browser/t_web_socket.dart @@ -15,7 +15,7 @@ /// specific language governing permissions and limitations /// under the License. -library thrift.src.html; +library thrift.src.browser; import 'dart:async'; import 'dart:convert' show Utf8Codec; @@ -38,12 +38,16 @@ class TWebSocket implements TSocket { final StreamController _onErrorController; Stream get onError => _onErrorController.stream; + final StreamController> _onMessageController; + Stream> get onMessage => _onMessageController.stream; + final List>> _completers = []; final List<_Request> _requests = []; TWebSocket(this.url) : _onStateController = new StreamController.broadcast(), - _onErrorController = new StreamController.broadcast() { + _onErrorController = new StreamController.broadcast(), + _onMessageController = new StreamController.broadcast() { if (url == null || !url.hasAuthority || !url.hasPort) { throw new ArgumentError("Invalid url"); } @@ -56,7 +60,7 @@ class TWebSocket implements TSocket { bool get isClosed => _socket == null || _socket.readyState == WebSocket.CLOSED; - void open() { + Future open() async { if (!isClosed) { throw new TTransportError( TTransportErrorType.ALREADY_OPEN, "Socket already connected"); @@ -69,7 +73,7 @@ class TWebSocket implements TSocket { _socket.onMessage.listen(_onMessage); } - void close() { + Future close() async { if (_socket != null) { _socket.close(); } @@ -117,6 +121,8 @@ class TWebSocket implements TSocket { if (!_completers.isEmpty) { _completers.removeAt(0).complete(data); } + + _onMessageController.add(data); } void _onError(Event event) { diff --git a/lib/dart/lib/src/console/t_web_socket.dart b/lib/dart/lib/src/console/t_web_socket.dart new file mode 100644 index 00000000000..9f4686a1f8a --- /dev/null +++ b/lib/dart/lib/src/console/t_web_socket.dart @@ -0,0 +1,110 @@ +/// 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. + +library thrift.src.console; + +import 'dart:async'; +import 'dart:convert' show Utf8Codec; +import 'dart:io'; + +import 'package:thrift/thrift.dart'; + +/// A [TSocket] backed by a [WebSocket] from dart:io +class TWebSocket implements TSocket { + static const utf8Codec = const Utf8Codec(); + + final StreamController _onStateController; + Stream get onState => _onStateController.stream; + + final StreamController _onErrorController; + Stream get onError => _onErrorController.stream; + + final StreamController> _onMessageController; + Stream> get onMessage => _onMessageController.stream; + + final List>> _completers = []; + + TWebSocket(WebSocket socket, {this.isServer: true}) + : _onStateController = new StreamController.broadcast(), + _onErrorController = new StreamController.broadcast(), + _onMessageController = new StreamController.broadcast() { + if (socket == null) { + throw new ArgumentError.notNull('socket'); + } + + _socket = socket; + _socket.listen(_onMessage, onError: _onError, onDone: close); + } + + final bool isServer; + + WebSocket _socket; + + bool get isOpen => _socket != null; + + bool get isClosed => _socket == null; + + Future open() async { + _onStateController.add(TSocketState.OPEN); + } + + Future close() async { + if (_socket != null) { + await _socket.close(); + _socket = null; + } + + _onStateController.add(TSocketState.CLOSED); + } + + Future> send(List data) async { + Future result; + if (isServer) { + result = new Future.value(); + } else { + // if we are a client, then we expect a result + Completer> completer = new Completer(); + _completers.add(completer); + result = completer.future; + } + + _socket.add(data); + + return result; + } + + void _onMessage(Object message) { + List data = message; + + if (message is String) { + data = utf8Codec.encode(message); + } else { + data = message; + } + + if (!_completers.isEmpty) { + _completers.removeAt(0).complete(data); + } + + _onMessageController.add(data); + } + + void _onError(Object error) { + close(); + _onErrorController.add("$error"); + } +} diff --git a/lib/dart/lib/src/transport/t_buffered_transport.dart b/lib/dart/lib/src/transport/t_buffered_transport.dart index 4fdc8db3fff..78b810aa647 100644 --- a/lib/dart/lib/src/transport/t_buffered_transport.dart +++ b/lib/dart/lib/src/transport/t_buffered_transport.dart @@ -32,7 +32,8 @@ class TBufferedTransport extends TTransport { _readIterator = readBuffer != null ? readBuffer.iterator : null; } - void _reset() { + void _reset({bool isOpen: false}) { + _isOpen = isOpen; _writeBuffer.clear(); _readIterator = null; } @@ -42,18 +43,12 @@ class TBufferedTransport extends TTransport { bool _isOpen; bool get isOpen => _isOpen; - void open() { - if (isOpen) { - throw new TTransportError( - TTransportErrorType.ALREADY_OPEN, "The transport is already open"); - } - _isOpen = true; - _reset(); + Future open() async { + _reset(isOpen: true); } - void close() { - _isOpen = false; - _reset(); + Future close() async { + _reset(isOpen: false); } int read(List buffer, int offset, int length) { diff --git a/lib/dart/lib/src/transport/t_framed_transport.dart b/lib/dart/lib/src/transport/t_framed_transport.dart index 58ccc73d2f8..82e01633b99 100644 --- a/lib/dart/lib/src/transport/t_framed_transport.dart +++ b/lib/dart/lib/src/transport/t_framed_transport.dart @@ -35,14 +35,14 @@ class TFramedTransport extends TBufferedTransport { bool get isOpen => _transport.isOpen; - void open() { - super.open(); - _transport.open(); + Future open() { + _reset(isOpen: true); + return _transport.open(); } - void close() { - super.close(); - _transport.close(); + Future close() { + _reset(isOpen: false); + return _transport.close(); } int read(List buffer, int offset, int length) { diff --git a/lib/dart/lib/src/transport/t_http_transport.dart b/lib/dart/lib/src/transport/t_http_transport.dart index 15c96b0fa45..731598d9204 100644 --- a/lib/dart/lib/src/transport/t_http_transport.dart +++ b/lib/dart/lib/src/transport/t_http_transport.dart @@ -29,7 +29,7 @@ part of thrift; /// /// Adapted from the JS XHR HTTP transport. class THttpClientTransport extends TBufferedTransport { - final http.BrowserClient httpClient; + final Client httpClient; final THttpConfig config; THttpClientTransport(this.httpClient, this.config) { @@ -38,8 +38,8 @@ class THttpClientTransport extends TBufferedTransport { } } - void close() { - super.close(); + Future close() async { + _reset(isOpen: false); httpClient.close(); } diff --git a/lib/dart/lib/src/transport/t_socket.dart b/lib/dart/lib/src/transport/t_socket.dart index 163dfc99a27..1c6c8b82d81 100644 --- a/lib/dart/lib/src/transport/t_socket.dart +++ b/lib/dart/lib/src/transport/t_socket.dart @@ -24,13 +24,15 @@ abstract class TSocket { Stream get onError; + Stream> get onMessage; + bool get isOpen; bool get isClosed; - void open(); + Future open(); - void close(); + Future close(); Future> send(List data); } diff --git a/lib/dart/lib/src/transport/t_socket_transport.dart b/lib/dart/lib/src/transport/t_socket_transport.dart index 22fbc1cf8e1..d02f596ab50 100644 --- a/lib/dart/lib/src/transport/t_socket_transport.dart +++ b/lib/dart/lib/src/transport/t_socket_transport.dart @@ -31,28 +31,47 @@ class TSocketTransport extends TBufferedTransport { final TSocket socket; final Logger log = new Logger('thrift.TSocketTransport'); - TSocketTransport(this.socket) { + final bool isListening; + + /// A transport using the provided [socket]. If [isListening] is true, then + /// messages received from [socket] will be written to the transport and made + /// available for reading. + TSocketTransport(this.socket, {isListening: false}) + : this.isListening = isListening { if (socket == null) { throw new ArgumentError.notNull("socket"); } socket.onError.listen((String e) => log.warning(e)); + + if (isListening) { + socket.onMessage.listen(_onMessage); + } } bool get isOpen => socket.isOpen; - void open() { - super.open(); - socket.open(); + Future open() { + _reset(isOpen: true); + return socket.open(); } - void close() { - super.close(); - socket.close(); + Future close() { + _reset(isOpen: false); + return socket.close(); } Future flush() async { - List result = await socket.send(_consumeWriteBuffer()); - _setReadBuffer(result); + if (isListening) { + _setReadBuffer(_consumeWriteBuffer()); + } else { + List result = await socket.send(_consumeWriteBuffer()); + _setReadBuffer(result); + } + } + + void _onMessage(List message) { + writeAll(message); + _setReadBuffer(_consumeWriteBuffer()); } } diff --git a/lib/dart/lib/src/transport/t_transport.dart b/lib/dart/lib/src/transport/t_transport.dart index e90269fabb8..863205a8840 100644 --- a/lib/dart/lib/src/transport/t_transport.dart +++ b/lib/dart/lib/src/transport/t_transport.dart @@ -24,10 +24,10 @@ abstract class TTransport { /// Opens the transport for reading/writing. /// Throws [TTransportError] if the transport could not be opened. - void open(); + Future open(); /// Closes the transport. - void close(); + Future close(); /// Reads up to [length] bytes into [buffer], starting at [offset]. /// Returns the number of bytes actually read. diff --git a/lib/dart/lib/thrift.dart b/lib/dart/lib/thrift.dart index 3d01aec76d0..f676a0fc7bc 100644 --- a/lib/dart/lib/thrift.dart +++ b/lib/dart/lib/thrift.dart @@ -1,3 +1,20 @@ +/// 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. + library thrift; import 'dart:async'; @@ -6,7 +23,7 @@ import 'dart:typed_data' show ByteData; import 'dart:typed_data' show Uint8List; import 'package:crypto/crypto.dart' show CryptoUtils; -import 'package:http/browser_client.dart' as http; +import 'package:http/http.dart' show Client; import 'package:logging/logging.dart'; part 'src/t_application_error.dart'; diff --git a/lib/dart/lib/thrift_browser.dart b/lib/dart/lib/thrift_browser.dart new file mode 100644 index 00000000000..2ebc2575868 --- /dev/null +++ b/lib/dart/lib/thrift_browser.dart @@ -0,0 +1,22 @@ +/// 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. + +library thrift_browser; + +/// Classes that are only supported in browser applications go here + +export 'src/browser/t_web_socket.dart' show TWebSocket; diff --git a/lib/dart/lib/thrift_console.dart b/lib/dart/lib/thrift_console.dart new file mode 100644 index 00000000000..d5edbd30a45 --- /dev/null +++ b/lib/dart/lib/thrift_console.dart @@ -0,0 +1,22 @@ +/// 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. + +library thrift_console; + +/// Classes that are only supported in console applications go here + +export 'src/console/t_web_socket.dart' show TWebSocket; diff --git a/lib/dart/lib/thrift_html.dart b/lib/dart/lib/thrift_html.dart deleted file mode 100644 index 7569a79a945..00000000000 --- a/lib/dart/lib/thrift_html.dart +++ /dev/null @@ -1,3 +0,0 @@ -library thrift_html; - -export 'src/html/t_web_socket.dart' show TWebSocket; diff --git a/tutorial/dart/.gitignore b/tutorial/dart/.gitignore new file mode 100644 index 00000000000..acda527ef2a --- /dev/null +++ b/tutorial/dart/.gitignore @@ -0,0 +1,4 @@ +.packages +packages +.pub/ +pubspec.lock diff --git a/tutorial/dart/build.sh b/tutorial/dart/build.sh new file mode 100755 index 00000000000..f6df69fe192 --- /dev/null +++ b/tutorial/dart/build.sh @@ -0,0 +1,7 @@ +rm -r gen-dart && \ +thrift --gen "dart:gen_server" "../shared.thrift" && \ + cd gen-dart/shared && pub get && cd ../.. && \ +thrift --gen "dart:gen_server" "../tutorial.thrift"; + cd gen-dart/tutorial && pub get && cd ../.. && \ +cd client && pub get && cd .. && \ +echo "Enjoy the Dart tutorial!" diff --git a/tutorial/dart/client/pubspec.yaml b/tutorial/dart/client/pubspec.yaml new file mode 100644 index 00000000000..bcab63a9cdc --- /dev/null +++ b/tutorial/dart/client/pubspec.yaml @@ -0,0 +1,17 @@ +name: tutorial_client +version: 0.1.0 +description: A Dart client implementation of the Apache Thrift tutorial +author: Mark Erickson +homepage: https://github.com/apache/thrift + +environment: + sdk: '>=1.0.0' + +dependencies: + browser: '>=0.10.0' + shared: + path: ../gen-dart/shared + thrift: + path: ../../../lib/dart + tutorial: + path: ../gen-dart/tutorial diff --git a/tutorial/dart/client/web/client.dart b/tutorial/dart/client/web/client.dart new file mode 100644 index 00000000000..96eda4dc6c1 --- /dev/null +++ b/tutorial/dart/client/web/client.dart @@ -0,0 +1,104 @@ +/// 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. + +import 'dart:html'; + +import 'package:thrift/thrift.dart'; +import 'package:thrift/thrift_browser.dart'; +import 'package:tutorial/tutorial.dart'; + +/// Adapted from the AS3 tutorial +void main() { + new CalculatorUI(querySelector('#output')).start(); +} + +class CalculatorUI { + final DivElement output; + + CalculatorUI(this.output); + + TTransport _transport; + Calculator _calculatorClient; + + void start() { + _buildInterface(); + _initConnection(); + } + + void _buildInterface() { + output.children.forEach((e) { + e.remove(); + }); + + output.append(new BRElement()); + ButtonElement pingButton = new ButtonElement() + ..text = "PING" + ..onClick.listen(_onPingClick); + output.append(pingButton); + output.append(new BRElement()); + + output.append(new BRElement()); + InputElement num1 = new InputElement() + ..id = "add1" + ..width = 50; + output.append(num1); + InputElement num2 = new InputElement() + ..id = "add2" + ..width = 50; + output.append(num2); + ButtonElement addButton = new ButtonElement() + ..text = "ADD" + ..onClick.listen(_onAddClick); + output.append(addButton); + output.append(new BRElement()); + } + + void _onPingClick(MouseEvent e) { + _validate(); + + _calculatorClient.ping(); + } + + void _onAddClick(MouseEvent e) { + _validate(); + + InputElement add1 = querySelector("#add1"); + InputElement add2 = querySelector("#add2"); + + _calculatorClient + .add(int.parse(add1.value), int.parse(add2.value)) + .then((int result) { + window.alert("The answer is $result"); + }); + } + + void _validate() { + if (!_transport.isOpen) { + window.alert("The transport is not open!"); + } + } + + void _initConnection() { + _transport = new TSocketTransport( + new TWebSocket(Uri.parse('ws://127.0.0.1:9090/ws'))); + TProtocol protocol = + new TBinaryProtocol(_transport, strictRead: false, strictWrite: false); + _transport.open(); + + _calculatorClient = new CalculatorClient(protocol); + } +} diff --git a/tutorial/dart/client/web/index.html b/tutorial/dart/client/web/index.html new file mode 100644 index 00000000000..6d62f58f078 --- /dev/null +++ b/tutorial/dart/client/web/index.html @@ -0,0 +1,18 @@ + + + + + + + Thrift Tutorial + + + + + + + +
+ + + diff --git a/tutorial/dart/client/web/styles.css b/tutorial/dart/client/web/styles.css new file mode 100644 index 00000000000..a39af230fa3 --- /dev/null +++ b/tutorial/dart/client/web/styles.css @@ -0,0 +1,9 @@ +@import url(https://fonts.googleapis.com/css?family=Roboto); + +html, body { + width: 100%; + height: 100%; + margin: 0; + padding: 10px; + font-family: 'Roboto', sans-serif; +} diff --git a/tutorial/dart/server/bin/main.dart b/tutorial/dart/server/bin/main.dart new file mode 100644 index 00000000000..a416fb5a9b7 --- /dev/null +++ b/tutorial/dart/server/bin/main.dart @@ -0,0 +1,87 @@ +import 'dart:async'; +import 'dart:convert' show Utf8Codec; +import 'dart:io'; + +import 'package:thrift/thrift.dart'; +import 'package:thrift/thrift_console.dart'; +import 'package:tutorial/tutorial.dart'; +import 'package:shared/shared.dart'; + +TProtocol _inputProtocol; +TProtocol _outputProtocol; +TProcessor _processor; +WebSocket _webSocket; + +final Utf8Codec _utf8Codec = new Utf8Codec(); + +main(List args) { + int port = 9090; + if (!args.isEmpty) { + port = int.parse(args[0]); + } + + _runServer(port); +} + +Future _runServer(int port) async { + var httpServer = await HttpServer.bind('127.0.0.1', port); + httpServer.listen((HttpRequest request) => _handleRequest(request)); + + print('listening for connections on $port'); +} + +Future _handleRequest(HttpRequest request) async { + if (request.uri.path == '/ws') { + await _initWebSocket(request); + } else { + print('Invalid path: ${request.uri.path}'); + } +} + +Future _initWebSocket(HttpRequest request) async { + _webSocket = await WebSocketTransformer.upgrade(request); + + TWebSocket socket = new TWebSocket(_webSocket); + + _processor = new CalculatorProcessor(new CalculatorServer()); + _inputProtocol = + new TBinaryProtocol(new TSocketTransport(socket, isListening: true)); + await _inputProtocol.transport.open(); + + _outputProtocol = new TBinaryProtocol(new TSocketTransport(socket)); + await _outputProtocol.transport.open(); + + socket.onError.listen((error) => print('Error: $error')); + socket.onMessage.listen(_onMessage); +} + +Future _onMessage(List data) async { + print('Received: ${_utf8Codec.decode(data)}'); + _processor.process(_inputProtocol, _outputProtocol); +} + +class CalculatorServer implements Calculator { + final Map _log = {}; + + Future ping() async { + print('ping()'); + } + + Future add(int num1, int num2) async { + print('add($num1, $num2)'); + return num1 + num2; + } + + Future calculate(int logid, Work w) { + return new Future.error(new UnimplementedError()); + } + + Future zip() async { + print('zip()'); + } + + Future getStruct(int key) async { + print('getStruct($key)'); + return _log[key]; + } +} diff --git a/tutorial/dart/server/pubspec.yaml b/tutorial/dart/server/pubspec.yaml new file mode 100644 index 00000000000..06b605cce06 --- /dev/null +++ b/tutorial/dart/server/pubspec.yaml @@ -0,0 +1,16 @@ +name: tutorial_server +version: 0.0.1 +description: A Dart server to support the Apache Thrift tutorial +author: Mark Erickson +homepage: https://github.com/apache/thrift + +environment: + sdk: '>=1.0.0' + +dependencies: + shared: + path: ../gen-dart/shared + thrift: + path: ../../../lib/dart + tutorial: + path: ../gen-dart/tutorial diff --git a/tutorial/shared.thrift b/tutorial/shared.thrift index 60e8e7adbd2..386000b17dc 100644 --- a/tutorial/shared.thrift +++ b/tutorial/shared.thrift @@ -24,6 +24,7 @@ namespace cpp shared namespace d share // "shared" would collide with the eponymous D keyword. +namespace dart shared namespace java shared namespace perl shared namespace php shared diff --git a/tutorial/tutorial.thrift b/tutorial/tutorial.thrift index 571ab8ef608..1149edfce54 100644 --- a/tutorial/tutorial.thrift +++ b/tutorial/tutorial.thrift @@ -64,6 +64,7 @@ include "shared.thrift" */ namespace cpp tutorial namespace d tutorial +namespace dart tutorial namespace java tutorial namespace php tutorial namespace perl tutorial From db111aecb8ccecc37a49af600db5cbb3f2c03449 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Thu, 10 Sep 2015 22:10:13 -0500 Subject: [PATCH 18/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Fix bugs in binary protocol and json protocol. Use base 64 encoding for socket and http transports. Implement the remainder of the tutorial. --- lib/dart/lib/src/browser/t_web_socket.dart | 15 +- lib/dart/lib/src/console/t_web_socket.dart | 17 +- .../lib/src/protocol/t_binary_protocol.dart | 6 +- .../lib/src/protocol/t_json_protocol.dart | 54 +++-- .../lib/src/transport/t_http_transport.dart | 17 +- tutorial/dart/build.sh | 2 + tutorial/dart/client/web/client.dart | 228 ++++++++++++++++-- tutorial/dart/server/bin/main.dart | 47 +++- 8 files changed, 306 insertions(+), 80 deletions(-) diff --git a/lib/dart/lib/src/browser/t_web_socket.dart b/lib/dart/lib/src/browser/t_web_socket.dart index 3810054edd7..49a528d0ba6 100644 --- a/lib/dart/lib/src/browser/t_web_socket.dart +++ b/lib/dart/lib/src/browser/t_web_socket.dart @@ -18,18 +18,16 @@ library thrift.src.browser; import 'dart:async'; -import 'dart:convert' show Utf8Codec; import 'dart:html' show CloseEvent; import 'dart:html' show Event; import 'dart:html' show MessageEvent; import 'dart:html' show WebSocket; +import 'package:crypto/crypto.dart' show CryptoUtils; import 'package:thrift/thrift.dart'; /// A [TSocket] backed by a [WebSocket] from dart:html class TWebSocket implements TSocket { - static const utf8Codec = const Utf8Codec(); - final Uri url; final StreamController _onStateController; @@ -92,7 +90,7 @@ class TWebSocket implements TSocket { while (isOpen && _requests.isNotEmpty) { _Request request = _requests.removeAt(0); _completers.add(request.completer); - _socket.sendString(utf8Codec.decode(request.data)); + _socket.sendString(CryptoUtils.bytesToBase64(request.data)); } } @@ -112,10 +110,11 @@ class TWebSocket implements TSocket { void _onMessage(MessageEvent event) { List data; - if (event.data is String) { - data = utf8Codec.encode(event.data); - } else { - data = event.data; + try { + data = CryptoUtils.base64StringToBytes(event.data); + } on FormatException catch (_) { + _onErrorController + .add(new UnsupportedError("Expected a Base 64 encoded string.")); } if (!_completers.isEmpty) { diff --git a/lib/dart/lib/src/console/t_web_socket.dart b/lib/dart/lib/src/console/t_web_socket.dart index 9f4686a1f8a..56d26744ac0 100644 --- a/lib/dart/lib/src/console/t_web_socket.dart +++ b/lib/dart/lib/src/console/t_web_socket.dart @@ -18,15 +18,13 @@ library thrift.src.console; import 'dart:async'; -import 'dart:convert' show Utf8Codec; import 'dart:io'; +import 'package:crypto/crypto.dart' show CryptoUtils; import 'package:thrift/thrift.dart'; /// A [TSocket] backed by a [WebSocket] from dart:io class TWebSocket implements TSocket { - static const utf8Codec = const Utf8Codec(); - final StreamController _onStateController; Stream get onState => _onStateController.stream; @@ -82,18 +80,19 @@ class TWebSocket implements TSocket { result = completer.future; } - _socket.add(data); + _socket.add(CryptoUtils.bytesToBase64(data)); return result; } void _onMessage(Object message) { - List data = message; + List data; - if (message is String) { - data = utf8Codec.encode(message); - } else { - data = message; + try { + data = CryptoUtils.base64StringToBytes(message); + } on FormatException catch (_) { + _onErrorController + .add(new UnsupportedError("Expected a Base 64 encoded string.")); } if (!_completers.isEmpty) { diff --git a/lib/dart/lib/src/protocol/t_binary_protocol.dart b/lib/dart/lib/src/protocol/t_binary_protocol.dart index 91db36acadb..f1e309e05d3 100644 --- a/lib/dart/lib/src/protocol/t_binary_protocol.dart +++ b/lib/dart/lib/src/protocol/t_binary_protocol.dart @@ -216,19 +216,19 @@ class TBinaryProtocol extends TProtocol { final Uint8List _i16In = new Uint8List(2); int readI16() { transport.readAll(_i16In, 0, 2); - return _i16In.buffer.asByteData().getUint16(0); + return _i16In.buffer.asByteData().getInt16(0); } final Uint8List _i32In = new Uint8List(4); int readI32() { transport.readAll(_i32In, 0, 4); - return _i32In.buffer.asByteData().getUint32(0); + return _i32In.buffer.asByteData().getInt32(0); } final Uint8List _i64In = new Uint8List(8); int readI64() { transport.readAll(_i64In, 0, 8); - return _i64In.buffer.asByteData().getUint64(0); + return _i64In.buffer.asByteData().getInt64(0); } final Uint8List _doubleIn = new Uint8List(8); diff --git a/lib/dart/lib/src/protocol/t_json_protocol.dart b/lib/dart/lib/src/protocol/t_json_protocol.dart index 3342321894f..064cf81a65c 100644 --- a/lib/dart/lib/src/protocol/t_json_protocol.dart +++ b/lib/dart/lib/src/protocol/t_json_protocol.dart @@ -28,21 +28,25 @@ class TJsonProtocol extends TProtocol { _BaseContext _context; _LookaheadReader _reader; - List<_BaseContext> contextStack = []; + final List<_BaseContext> _contextStack = []; final List _tempBuffer = new List(4); TJsonProtocol(TTransport transport) : super(transport) { - _context = new _BaseContext(this); _reader = new _LookaheadReader(this); } void _pushContext(_BaseContext c) { - contextStack.add(c); + _contextStack.add(c); _context = c; } void _popContext() { - _context = contextStack.removeLast(); + _context = _contextStack.removeLast(); + } + + void _resetContext() { + _contextStack.clear(); + _context = new _BaseContext(this); } /// Read a byte that must match [char]; otherwise throw a [TProtocolError]. @@ -51,7 +55,7 @@ class TJsonProtocol extends TProtocol { int byte = _reader.read(); if (byte != charByte) { throw new TProtocolError(TProtocolErrorType.INVALID_DATA, - "Unexpected character: ${new String.fromCharCode(byte)}"); + "Expected character $char but found: ${new String.fromCharCode(byte)}"); } } @@ -95,7 +99,7 @@ class TJsonProtocol extends TProtocol { if (_tempBuffer[0] == 1) { transport.write(bytes, i, 1); } else if (_tempBuffer[0] > 1) { - transport.write(_Constants.BACKSLASH.codeUnits, i, 1); + transport.writeAll(_Constants.BACKSLASH.codeUnits); transport.write(_tempBuffer, 0, 1); } else { transport.writeAll(_Constants.ESCSEQ.codeUnits); @@ -169,6 +173,8 @@ class TJsonProtocol extends TProtocol { } void writeMessageBegin(TMessage message) { + _resetContext(); + _writeJsonArrayStart(); _writeJsonInteger(VERSION_1); @@ -319,11 +325,10 @@ class TJsonProtocol extends TProtocol { String _readJsonNumericChars() { StringBuffer buffer = new StringBuffer(); while (true) { - int byte = _reader.peek(); - if (!_Constants.isJsonNumeric(byte)) { + if (!_Constants.isJsonNumeric(_reader.peek())) { break; } - buffer.write(new String.fromCharCode(byte)); + buffer.write(new String.fromCharCode(_reader.read())); } return buffer.toString(); } @@ -402,6 +407,8 @@ class TJsonProtocol extends TProtocol { } TMessage readMessageBegin() { + _resetContext(); + _readJsonArrayStart(); if (_readJsonInteger() != VERSION_1) { throw new TProtocolError( @@ -430,14 +437,13 @@ class TJsonProtocol extends TProtocol { } TField readFieldBegin() { - int byte = _reader.peek(); - String name = ""; int type = TType.STOP; int id = 0; - if (byte != _Constants.RBRACE.codeUnitAt(0)) { + if (_reader.peek() != _Constants.RBRACE.codeUnitAt(0)) { id = _readJsonInteger(); + _readJsonObjectStart(); type = _Constants.getTypeIdForTypeName(_readJsonString()); } @@ -586,18 +592,18 @@ class _Constants { return _TYPE_ID_TO_NAME[typeId]; } - static final Map, int> _NAME_TO_TYPE_ID = new Map.unmodifiable({ - NAME_BOOL.codeUnits: TType.BOOL, - NAME_BYTE.codeUnits: TType.BYTE, - NAME_I16.codeUnits: TType.I16, - NAME_I32.codeUnits: TType.I32, - NAME_I64.codeUnits: TType.I64, - NAME_DOUBLE.codeUnits: TType.DOUBLE, - NAME_STRING.codeUnits: TType.STRING, - NAME_STRUCT.codeUnits: TType.STRUCT, - NAME_MAP.codeUnits: TType.MAP, - NAME_SET.codeUnits: TType.SET, - NAME_LIST.codeUnits: TType.LIST + static final Map _NAME_TO_TYPE_ID = new Map.unmodifiable({ + NAME_BOOL: TType.BOOL, + NAME_BYTE: TType.BYTE, + NAME_I16: TType.I16, + NAME_I32: TType.I32, + NAME_I64: TType.I64, + NAME_DOUBLE: TType.DOUBLE, + NAME_STRING: TType.STRING, + NAME_STRUCT: TType.STRUCT, + NAME_MAP: TType.MAP, + NAME_SET: TType.SET, + NAME_LIST: TType.LIST }); static int getTypeIdForTypeName(List bytes) { diff --git a/lib/dart/lib/src/transport/t_http_transport.dart b/lib/dart/lib/src/transport/t_http_transport.dart index 731598d9204..0c9f4653d6c 100644 --- a/lib/dart/lib/src/transport/t_http_transport.dart +++ b/lib/dart/lib/src/transport/t_http_transport.dart @@ -44,10 +44,19 @@ class THttpClientTransport extends TBufferedTransport { } Future flush() async { - var body = _consumeWriteBuffer(); - var response = - await httpClient.post(config.url, headers: config.headers, body: body); - _setReadBuffer(response.bodyBytes); + var requestBody = CryptoUtils.bytesToBase64(_consumeWriteBuffer()); + var response = await httpClient.post(config.url, + headers: config.headers, body: requestBody); + + List data; + try { + data = CryptoUtils.base64StringToBytes(response.body); + } on FormatException catch (_) { + throw new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Expected a Base 64 encoded string."); + } + + _setReadBuffer(data); } } diff --git a/tutorial/dart/build.sh b/tutorial/dart/build.sh index f6df69fe192..bac4d2eb961 100755 --- a/tutorial/dart/build.sh +++ b/tutorial/dart/build.sh @@ -4,4 +4,6 @@ thrift --gen "dart:gen_server" "../shared.thrift" && \ thrift --gen "dart:gen_server" "../tutorial.thrift"; cd gen-dart/tutorial && pub get && cd ../.. && \ cd client && pub get && cd .. && \ +cd server && pub get && cd .. && \ +dartfmt -w gen-dart && \ echo "Enjoy the Dart tutorial!" diff --git a/tutorial/dart/client/web/client.dart b/tutorial/dart/client/web/client.dart index 96eda4dc6c1..bfb5be032be 100644 --- a/tutorial/dart/client/web/client.dart +++ b/tutorial/dart/client/web/client.dart @@ -19,6 +19,7 @@ import 'dart:html'; import 'package:thrift/thrift.dart'; import 'package:thrift/thrift_browser.dart'; +import 'package:shared/shared.dart'; import 'package:tutorial/tutorial.dart'; /// Adapted from the AS3 tutorial @@ -39,66 +40,245 @@ class CalculatorUI { _initConnection(); } + void _validate() { + if (!_transport.isOpen) { + window.alert("The transport is not open!"); + } + } + + void _initConnection() { + _transport = new TSocketTransport( + new TWebSocket(Uri.parse('ws://127.0.0.1:9090/ws'))); + TProtocol protocol = new TJsonProtocol(_transport); + _transport.open(); + + _calculatorClient = new CalculatorClient(protocol); + } + void _buildInterface() { output.children.forEach((e) { e.remove(); }); - output.append(new BRElement()); + _buildPingComponent(); + + _buildAddComponent(); + + _buildCalculatorComponent(); + + _buildGetStructComponent(); + } + + void _buildPingComponent() { + output.append(new HeadingElement.h3()..text = "Ping"); ButtonElement pingButton = new ButtonElement() ..text = "PING" ..onClick.listen(_onPingClick); output.append(pingButton); output.append(new BRElement()); + } + + void _onPingClick(MouseEvent e) { + _validate(); + _calculatorClient.ping(); + } + + void _buildAddComponent() { output.append(new BRElement()); + output.append(new HRElement()); + output.append(new HeadingElement.h3()..text = "Add"); InputElement num1 = new InputElement() ..id = "add1" - ..width = 50; + ..type = "number" + ..style.fontSize = "14px" + ..style.width = "50px"; output.append(num1); + SpanElement op = new SpanElement() + ..text = "+" + ..style.fontSize = "14px" + ..style.marginLeft = "10px"; + output.append(op); InputElement num2 = new InputElement() ..id = "add2" - ..width = 50; + ..type = "number" + ..style.fontSize = "14px" + ..style.width = "50px" + ..style.marginLeft = "10px"; output.append(num2); ButtonElement addButton = new ButtonElement() - ..text = "ADD" + ..text = "=" + ..style.fontSize = "14px" + ..style.marginLeft = "10px" ..onClick.listen(_onAddClick); output.append(addButton); + SpanElement result = new SpanElement() + ..id = "addResult" + ..style.fontSize = "14px" + ..style.marginLeft = "10px"; + output.append(result); output.append(new BRElement()); } - void _onPingClick(MouseEvent e) { + void _onAddClick(MouseEvent e) { _validate(); - _calculatorClient.ping(); + InputElement num1 = querySelector("#add1"); + InputElement num2 = querySelector("#add2"); + SpanElement result = querySelector("#addResult"); + + _calculatorClient + .add(int.parse(num1.value), int.parse(num2.value)) + .then((int n) { + result.text = "$n"; + }); } - void _onAddClick(MouseEvent e) { + void _buildCalculatorComponent() { + output.append(new BRElement()); + output.append(new HRElement()); + output.append(new HeadingElement.h3()..text = "Calculator"); + InputElement num1 = new InputElement() + ..id = "calc1" + ..type = "number" + ..style.fontSize = "14px" + ..style.width = "50px"; + output.append(num1); + SelectElement op = new SelectElement() + ..id = "calcOp" + ..multiple = false + ..selectedIndex = 0 + ..style.fontSize = "16px" + ..style.marginLeft = "10px" + ..style.width = "50px"; + OptionElement addOp = new OptionElement() + ..text = "+" + ..value = Operation.ADD.toString(); + op.add(addOp, 0); + OptionElement subtractOp = new OptionElement() + ..text = "-" + ..value = Operation.SUBTRACT.toString(); + op.add(subtractOp, 1); + OptionElement multiplyOp = new OptionElement() + ..text = "*" + ..value = Operation.MULTIPLY.toString(); + op.add(multiplyOp, 2); + OptionElement divideOp = new OptionElement() + ..text = "/" + ..value = Operation.DIVIDE.toString(); + op.add(divideOp, 3); + output.append(op); + InputElement num2 = new InputElement() + ..id = "calc2" + ..type = "number" + ..style.fontSize = "14px" + ..style.width = "50px" + ..style.marginLeft = "10px"; + output.append(num2); + ButtonElement calcButton = new ButtonElement() + ..text = "=" + ..style.fontSize = "14px" + ..style.marginLeft = "10px" + ..onClick.listen(_onCalcClick); + output.append(calcButton); + SpanElement result = new SpanElement() + ..id = "calcResult" + ..style.fontSize = "14px" + ..style.marginLeft = "10px"; + output.append(result); + output.append(new BRElement()); + output.append(new BRElement()); + LabelElement logIdLabel = new LabelElement() + ..text = "Log ID:" + ..style.fontSize = "14px"; + output.append(logIdLabel); + InputElement logId = new InputElement() + ..id = "logId" + ..type = "number" + ..value = "1" + ..style.fontSize = "14px" + ..style.width = "50px" + ..style.marginLeft = "10px"; + output.append(logId); + LabelElement commentLabel = new LabelElement() + ..text = "Comment:" + ..style.fontSize = "14px" + ..style.marginLeft = "10px"; + output.append(commentLabel); + InputElement comment = new InputElement() + ..id = "comment" + ..style.fontSize = "14px" + ..style.width = "100px" + ..style.marginLeft = "10px"; + output.append(comment); + output.append(new BRElement()); + } + + void _onCalcClick(MouseEvent e) { _validate(); - InputElement add1 = querySelector("#add1"); - InputElement add2 = querySelector("#add2"); + InputElement num1 = querySelector("#calc1"); + InputElement num2 = querySelector("#calc2"); + SelectElement op = querySelector("#calcOp"); + SpanElement result = querySelector("#calcResult"); + SelectElement logId = querySelector("#logId"); + InputElement comment = querySelector("#comment"); - _calculatorClient - .add(int.parse(add1.value), int.parse(add2.value)) - .then((int result) { - window.alert("The answer is $result"); + Work work = new Work(); + work.num1 = int.parse(num1.value); + work.num2 = int.parse(num2.value); + work.op = int.parse(op.options[op.selectedIndex].value); + work.comment = comment.value; + + _calculatorClient.calculate(int.parse(logId.value), work).then((int n) { + result.text = "$n"; }); } - void _validate() { - if (!_transport.isOpen) { - window.alert("The transport is not open!"); - } + void _buildGetStructComponent() { + output.append(new BRElement()); + output.append(new HRElement()); + output.append(new HeadingElement.h3()..text = "Get Struct"); + LabelElement logIdLabel = new LabelElement() + ..text = "Struct Key:" + ..style.fontSize = "14px"; + output.append(logIdLabel); + InputElement logId = new InputElement() + ..id = "structKey" + ..type = "number" + ..value = "1" + ..style.fontSize = "14px" + ..style.width = "50px" + ..style.marginLeft = "10px"; + output.append(logId); + ButtonElement getStructButton = new ButtonElement() + ..text = "GET" + ..style.fontSize = "14px" + ..style.marginLeft = "10px" + ..onClick.listen(_onGetStructClick); + output.append(getStructButton); + output.append(new BRElement()); + output.append(new BRElement()); + TextAreaElement result = new TextAreaElement() + ..id = "getStructResult" + ..style.fontSize = "14px" + ..style.width = "300px" + ..style.height = "50px" + ..style.marginLeft = "10px"; + output.append(result); + output.append(new BRElement()); } - void _initConnection() { - _transport = new TSocketTransport( - new TWebSocket(Uri.parse('ws://127.0.0.1:9090/ws'))); - TProtocol protocol = - new TBinaryProtocol(_transport, strictRead: false, strictWrite: false); - _transport.open(); + void _onGetStructClick(MouseEvent e) { + _validate(); - _calculatorClient = new CalculatorClient(protocol); + InputElement structKey = querySelector("#structKey"); + SpanElement result = querySelector("#getStructResult"); + + _calculatorClient + .getStruct(int.parse(structKey.value)) + .then((SharedStruct s) { + result.text = "${s.toString()}"; + }); } } diff --git a/tutorial/dart/server/bin/main.dart b/tutorial/dart/server/bin/main.dart index a416fb5a9b7..d78f60e6f64 100644 --- a/tutorial/dart/server/bin/main.dart +++ b/tutorial/dart/server/bin/main.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:convert' show Utf8Codec; import 'dart:io'; import 'package:thrift/thrift.dart'; @@ -12,8 +11,6 @@ TProtocol _outputProtocol; TProcessor _processor; WebSocket _webSocket; -final Utf8Codec _utf8Codec = new Utf8Codec(); - main(List args) { int port = 9090; if (!args.isEmpty) { @@ -45,10 +42,10 @@ Future _initWebSocket(HttpRequest request) async { _processor = new CalculatorProcessor(new CalculatorServer()); _inputProtocol = - new TBinaryProtocol(new TSocketTransport(socket, isListening: true)); + new TJsonProtocol(new TSocketTransport(socket, isListening: true)); await _inputProtocol.transport.open(); - _outputProtocol = new TBinaryProtocol(new TSocketTransport(socket)); + _outputProtocol = new TJsonProtocol(new TSocketTransport(socket)); await _outputProtocol.transport.open(); socket.onError.listen((error) => print('Error: $error')); @@ -56,7 +53,6 @@ Future _initWebSocket(HttpRequest request) async { } Future _onMessage(List data) async { - print('Received: ${_utf8Codec.decode(data)}'); _processor.process(_inputProtocol, _outputProtocol); } @@ -69,11 +65,45 @@ class CalculatorServer implements Calculator { Future add(int num1, int num2) async { print('add($num1, $num2)'); + return num1 + num2; } - Future calculate(int logid, Work w) { - return new Future.error(new UnimplementedError()); + Future calculate(int logid, Work work) async { + print('calulate($logid, ${work.toString()})'); + + int val; + + switch (work.op) { + case Operation.ADD: + val = work.num1 + work.num2; + break; + + case Operation.SUBTRACT: + val = work.num1 - work.num2; + break; + + case Operation.MULTIPLY: + val = work.num1 * work.num2; + break; + + case Operation.DIVIDE: + if (work.num2 == 0) { + var x = new InvalidOperation(); + x.whatOp = work.op; + x.why = 'Cannot divide by 0'; + throw x; + } + val = (work.num1 / work.num2).floor(); + break; + } + + var log = new SharedStruct(); + log.key = logid; + log.value = '$val "${work.comment}"'; + this._log[logid] = log; + + return val; } Future zip() async { @@ -82,6 +112,7 @@ class CalculatorServer implements Calculator { Future getStruct(int key) async { print('getStruct($key)'); + return _log[key]; } } From c18f92c4297db61a404617675191b0ecd3034d15 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Fri, 11 Sep 2015 13:20:10 -0500 Subject: [PATCH 19/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Make pubspec dependencies more consistent and restrictive. --- compiler/cpp/src/generate/t_dart_generator.cc | 3 +-- lib/dart/pubspec.yaml | 12 ++++++------ tutorial/dart/build.sh | 6 ++++-- tutorial/dart/client/pubspec.yaml | 4 ++-- tutorial/dart/server/pubspec.yaml | 2 +- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/compiler/cpp/src/generate/t_dart_generator.cc b/compiler/cpp/src/generate/t_dart_generator.cc index 42f933c2838..360538d3b9e 100644 --- a/compiler/cpp/src/generate/t_dart_generator.cc +++ b/compiler/cpp/src/generate/t_dart_generator.cc @@ -352,13 +352,12 @@ void t_dart_generator::generate_dart_pubspec() { indent(f_pubspec) << "environment:" << endl; indent_up(); - indent(f_pubspec) << "sdk: '>=1.12.0'" << endl; + indent(f_pubspec) << "sdk: ^1.12.0" << endl; indent_down(); f_pubspec << endl; indent(f_pubspec) << "dependencies:" << endl; indent_up(); - indent(f_pubspec) << "logging: '>=0.9.0 <0.12.0'" << endl; indent(f_pubspec) << "thrift: # '>=" << dart_thrift_version << "'" << endl; indent_up(); indent(f_pubspec) << "path: ../../../../lib/dart" << endl; diff --git a/lib/dart/pubspec.yaml b/lib/dart/pubspec.yaml index d59fd625af4..b1f0919b58a 100644 --- a/lib/dart/pubspec.yaml +++ b/lib/dart/pubspec.yaml @@ -5,11 +5,11 @@ description: > author: Mark Erickson homepage: https://github.com/apache/thrift documentation: https://github.com/apache/thrift +environment: + sdk: ^1.12.0 dependencies: - crypto: '>=0.9.0' - http: '>=0.11.3' - logging: '>=0.11.0' + crypto: ^0.9.0 + http: ^0.11.3 + logging: ^0.11.0 dev_dependencies: - test: '>=0.12.0' -environment: - sdk: '>=1.12.0' + test: ^0.12.0 diff --git a/tutorial/dart/build.sh b/tutorial/dart/build.sh index bac4d2eb961..4eed80b5f0f 100755 --- a/tutorial/dart/build.sh +++ b/tutorial/dart/build.sh @@ -1,7 +1,9 @@ -rm -r gen-dart && \ +#!/bin/sh + +rm -r gen-dart || true && \ thrift --gen "dart:gen_server" "../shared.thrift" && \ cd gen-dart/shared && pub get && cd ../.. && \ -thrift --gen "dart:gen_server" "../tutorial.thrift"; +thrift --gen "dart:gen_server" "../tutorial.thrift" && \ cd gen-dart/tutorial && pub get && cd ../.. && \ cd client && pub get && cd .. && \ cd server && pub get && cd .. && \ diff --git a/tutorial/dart/client/pubspec.yaml b/tutorial/dart/client/pubspec.yaml index bcab63a9cdc..c0cde1eec85 100644 --- a/tutorial/dart/client/pubspec.yaml +++ b/tutorial/dart/client/pubspec.yaml @@ -5,10 +5,10 @@ author: Mark Erickson homepage: https://github.com/apache/thrift environment: - sdk: '>=1.0.0' + sdk: ^1.12.0 dependencies: - browser: '>=0.10.0' + browser: ^0.10.0 shared: path: ../gen-dart/shared thrift: diff --git a/tutorial/dart/server/pubspec.yaml b/tutorial/dart/server/pubspec.yaml index 06b605cce06..b28f3dc1245 100644 --- a/tutorial/dart/server/pubspec.yaml +++ b/tutorial/dart/server/pubspec.yaml @@ -5,7 +5,7 @@ author: Mark Erickson homepage: https://github.com/apache/thrift environment: - sdk: '>=1.0.0' + sdk: ^1.12.0 dependencies: shared: From 3e763612f528afb117bc8284c938dbee9944a272 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Fri, 11 Sep 2015 15:12:08 -0500 Subject: [PATCH 20/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Switch uses of List to Uint8List to be more explicit. --- lib/dart/lib/src/browser/t_web_socket.dart | 19 ++++++------ lib/dart/lib/src/console/t_web_socket.dart | 15 +++++----- .../lib/src/protocol/t_json_protocol.dart | 30 ++++++++++--------- .../src/transport/t_buffered_transport.dart | 10 +++---- .../lib/src/transport/t_framed_transport.dart | 6 ++-- .../lib/src/transport/t_http_transport.dart | 4 +-- lib/dart/lib/src/transport/t_socket.dart | 4 +-- .../lib/src/transport/t_socket_transport.dart | 4 +-- lib/dart/lib/src/transport/t_transport.dart | 8 ++--- tutorial/dart/client/web/client.dart | 15 +++------- tutorial/dart/client/web/styles.css | 6 ++++ tutorial/dart/server/bin/main.dart | 3 +- 12 files changed, 64 insertions(+), 60 deletions(-) diff --git a/lib/dart/lib/src/browser/t_web_socket.dart b/lib/dart/lib/src/browser/t_web_socket.dart index 49a528d0ba6..50e78446ca4 100644 --- a/lib/dart/lib/src/browser/t_web_socket.dart +++ b/lib/dart/lib/src/browser/t_web_socket.dart @@ -22,6 +22,7 @@ import 'dart:html' show CloseEvent; import 'dart:html' show Event; import 'dart:html' show MessageEvent; import 'dart:html' show WebSocket; +import 'dart:typed_data' show Uint8List; import 'package:crypto/crypto.dart' show CryptoUtils; import 'package:thrift/thrift.dart'; @@ -36,10 +37,10 @@ class TWebSocket implements TSocket { final StreamController _onErrorController; Stream get onError => _onErrorController.stream; - final StreamController> _onMessageController; - Stream> get onMessage => _onMessageController.stream; + final StreamController _onMessageController; + Stream get onMessage => _onMessageController.stream; - final List>> _completers = []; + final List> _completers = []; final List<_Request> _requests = []; TWebSocket(this.url) @@ -77,8 +78,8 @@ class TWebSocket implements TSocket { } } - Future> send(List data) { - Completer> completer = new Completer(); + Future send(Uint8List data) { + Completer completer = new Completer(); _requests.add(new _Request(data, completer)); _sendRequests(); @@ -108,10 +109,10 @@ class TWebSocket implements TSocket { } void _onMessage(MessageEvent event) { - List data; + Uint8List data; try { - data = CryptoUtils.base64StringToBytes(event.data); + data = new Uint8List.fromList(CryptoUtils.base64StringToBytes(event.data)); } on FormatException catch (_) { _onErrorController .add(new UnsupportedError("Expected a Base 64 encoded string.")); @@ -131,8 +132,8 @@ class TWebSocket implements TSocket { } class _Request { - final List data; - final Completer> completer; + final Uint8List data; + final Completer completer; _Request(this.data, this.completer); } diff --git a/lib/dart/lib/src/console/t_web_socket.dart b/lib/dart/lib/src/console/t_web_socket.dart index 56d26744ac0..3d867ba0086 100644 --- a/lib/dart/lib/src/console/t_web_socket.dart +++ b/lib/dart/lib/src/console/t_web_socket.dart @@ -19,6 +19,7 @@ library thrift.src.console; import 'dart:async'; import 'dart:io'; +import 'dart:typed_data' show Uint8List; import 'package:crypto/crypto.dart' show CryptoUtils; import 'package:thrift/thrift.dart'; @@ -31,10 +32,10 @@ class TWebSocket implements TSocket { final StreamController _onErrorController; Stream get onError => _onErrorController.stream; - final StreamController> _onMessageController; - Stream> get onMessage => _onMessageController.stream; + final StreamController _onMessageController; + Stream get onMessage => _onMessageController.stream; - final List>> _completers = []; + final List> _completers = []; TWebSocket(WebSocket socket, {this.isServer: true}) : _onStateController = new StreamController.broadcast(), @@ -69,13 +70,13 @@ class TWebSocket implements TSocket { _onStateController.add(TSocketState.CLOSED); } - Future> send(List data) async { + Future send(Uint8List data) async { Future result; if (isServer) { result = new Future.value(); } else { // if we are a client, then we expect a result - Completer> completer = new Completer(); + Completer completer = new Completer(); _completers.add(completer); result = completer.future; } @@ -86,10 +87,10 @@ class TWebSocket implements TSocket { } void _onMessage(Object message) { - List data; + Uint8List data; try { - data = CryptoUtils.base64StringToBytes(message); + data = new Uint8List.fromList(CryptoUtils.base64StringToBytes(message)); } on FormatException catch (_) { _onErrorController .add(new UnsupportedError("Expected a Base 64 encoded string.")); diff --git a/lib/dart/lib/src/protocol/t_json_protocol.dart b/lib/dart/lib/src/protocol/t_json_protocol.dart index 064cf81a65c..42be174e40e 100644 --- a/lib/dart/lib/src/protocol/t_json_protocol.dart +++ b/lib/dart/lib/src/protocol/t_json_protocol.dart @@ -29,7 +29,7 @@ class TJsonProtocol extends TProtocol { _LookaheadReader _reader; final List<_BaseContext> _contextStack = []; - final List _tempBuffer = new List(4); + final Uint8List _tempBuffer = new Uint8List(4); TJsonProtocol(TTransport transport) : super(transport) { _reader = new _LookaheadReader(this); @@ -80,7 +80,7 @@ class TJsonProtocol extends TProtocol { /// write /// Write the [bytes] as JSON characters, escaping as needed. - void _writeJsonString(List bytes) { + void _writeJsonString(Uint8List bytes) { _context.write(); transport.writeAll(_Constants.QUOTE.codeUnits); @@ -140,7 +140,7 @@ class TJsonProtocol extends TProtocol { } } - void _writeJsonBase64(List bytes) { + void _writeJsonBase64(Uint8List bytes) { _context.write(); transport.writeAll(_Constants.QUOTE.codeUnits); @@ -276,7 +276,7 @@ class TJsonProtocol extends TProtocol { /// read - List _readJsonString({bool skipContext: false}) { + Uint8List _readJsonString({bool skipContext: false}) { List bytes = []; if (!skipContext) { @@ -319,7 +319,7 @@ class TJsonProtocol extends TProtocol { bytes.add(byte); } - return bytes; + return new Uint8List.fromList(bytes); } String _readJsonNumericChars() { @@ -356,7 +356,7 @@ class TJsonProtocol extends TProtocol { _context.read(); if (_reader.peek() == _Constants.QUOTE.codeUnitAt(0)) { - List bytes = _readJsonString(skipContext: true); + Uint8List bytes = _readJsonString(skipContext: true); double d = double.parse(utf8Codec.decode(bytes), (_) { throw new TProtocolError(TProtocolErrorType.INVALID_DATA, "Bad data encounted in numeric data"); @@ -378,10 +378,12 @@ class TJsonProtocol extends TProtocol { } } - List _readJsonBase64() { - List bytes = _readJsonString(); - String base64 = utf8Codec.decode(bytes); - return CryptoUtils.base64StringToBytes(base64); + Uint8List _readJsonBase64() { + // convert UTF-8 bytes of a Base 64 encoded string to binary bytes + Uint8List base64Bytes = _readJsonString(); + String base64 = utf8Codec.decode(base64Bytes); + + return new Uint8List.fromList(CryptoUtils.base64StringToBytes(base64)); } void _readJsonObjectStart() { @@ -415,7 +417,7 @@ class TJsonProtocol extends TProtocol { TProtocolErrorType.BAD_VERSION, "Message contained bad version."); } - List buffer = _readJsonString(); + Uint8List buffer = _readJsonString(); String name = utf8Codec.decode(buffer); int type = _readJsonInteger(); int seqid = _readJsonInteger(); @@ -544,7 +546,7 @@ class _Constants { static const String ESCSEQ = r'\u00'; - static final List JSON_CHAR_TABLE = new List.unmodifiable([ + static final Uint8List JSON_CHAR_TABLE = new Uint8List.fromList([ 0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes 'b'.codeUnitAt(0), 't'.codeUnitAt(0), 'n'.codeUnitAt(0), 0, // 4 bytes 'f'.codeUnitAt(0), 'r'.codeUnitAt(0), 0, 0, // 4 bytes @@ -606,7 +608,7 @@ class _Constants { NAME_LIST: TType.LIST }); - static int getTypeIdForTypeName(List bytes) { + static int getTypeIdForTypeName(Uint8List bytes) { String name = utf8codec.decode(bytes); if (!_NAME_TO_TYPE_ID.containsKey(name)) { throw new TProtocolError( @@ -645,7 +647,7 @@ class _LookaheadReader { _LookaheadReader(this.protocol); bool _hasData; - final List _data = new List(1); + final Uint8List _data = new Uint8List(1); int read() { if (_hasData) { diff --git a/lib/dart/lib/src/transport/t_buffered_transport.dart b/lib/dart/lib/src/transport/t_buffered_transport.dart index 78b810aa647..1d44c621dd9 100644 --- a/lib/dart/lib/src/transport/t_buffered_transport.dart +++ b/lib/dart/lib/src/transport/t_buffered_transport.dart @@ -22,13 +22,13 @@ class TBufferedTransport extends TTransport { final List _writeBuffer = []; Iterator _readIterator; - List _consumeWriteBuffer() { - List buffer = new List.from(_writeBuffer, growable: false); + Uint8List _consumeWriteBuffer() { + Uint8List buffer = new Uint8List.fromList(_writeBuffer); _writeBuffer.clear(); return buffer; } - void _setReadBuffer(List readBuffer) { + void _setReadBuffer(Uint8List readBuffer) { _readIterator = readBuffer != null ? readBuffer.iterator : null; } @@ -51,7 +51,7 @@ class TBufferedTransport extends TTransport { _reset(isOpen: false); } - int read(List buffer, int offset, int length) { + int read(Uint8List buffer, int offset, int length) { if (buffer == null) { throw new ArgumentError.notNull("buffer"); } @@ -78,7 +78,7 @@ class TBufferedTransport extends TTransport { return i; } - void write(List buffer, int offset, int length) { + void write(Uint8List buffer, int offset, int length) { if (buffer == null) { throw new ArgumentError.notNull("buffer"); } diff --git a/lib/dart/lib/src/transport/t_framed_transport.dart b/lib/dart/lib/src/transport/t_framed_transport.dart index 82e01633b99..c2a57a4a733 100644 --- a/lib/dart/lib/src/transport/t_framed_transport.dart +++ b/lib/dart/lib/src/transport/t_framed_transport.dart @@ -45,7 +45,7 @@ class TFramedTransport extends TBufferedTransport { return _transport.close(); } - int read(List buffer, int offset, int length) { + int read(Uint8List buffer, int offset, int length) { if (hasReadData) { int got = super.read(buffer, offset, length); if (got > 0) return got; @@ -65,13 +65,13 @@ class TFramedTransport extends TBufferedTransport { TTransportErrorType.UNKNOWN, "Read a negative frame size: $size"); } - List buffer = new List(size); + Uint8List buffer = new Uint8List(size); _transport.readAll(buffer, 0, size); _setReadBuffer(buffer); } Future flush() { - List buffer = _consumeWriteBuffer(); + Uint8List buffer = _consumeWriteBuffer(); int length = buffer.length; headerBytes.buffer.asByteData().setUint32(0, length); diff --git a/lib/dart/lib/src/transport/t_http_transport.dart b/lib/dart/lib/src/transport/t_http_transport.dart index 0c9f4653d6c..6b7609de15a 100644 --- a/lib/dart/lib/src/transport/t_http_transport.dart +++ b/lib/dart/lib/src/transport/t_http_transport.dart @@ -48,9 +48,9 @@ class THttpClientTransport extends TBufferedTransport { var response = await httpClient.post(config.url, headers: config.headers, body: requestBody); - List data; + Uint8List data; try { - data = CryptoUtils.base64StringToBytes(response.body); + data = new Uint8List.fromList(CryptoUtils.base64StringToBytes(response.body)); } on FormatException catch (_) { throw new TProtocolError(TProtocolErrorType.INVALID_DATA, "Expected a Base 64 encoded string."); diff --git a/lib/dart/lib/src/transport/t_socket.dart b/lib/dart/lib/src/transport/t_socket.dart index 1c6c8b82d81..c299d350410 100644 --- a/lib/dart/lib/src/transport/t_socket.dart +++ b/lib/dart/lib/src/transport/t_socket.dart @@ -24,7 +24,7 @@ abstract class TSocket { Stream get onError; - Stream> get onMessage; + Stream get onMessage; bool get isOpen; @@ -34,5 +34,5 @@ abstract class TSocket { Future close(); - Future> send(List data); + Future send(Uint8List data); } diff --git a/lib/dart/lib/src/transport/t_socket_transport.dart b/lib/dart/lib/src/transport/t_socket_transport.dart index d02f596ab50..9cfc22a833d 100644 --- a/lib/dart/lib/src/transport/t_socket_transport.dart +++ b/lib/dart/lib/src/transport/t_socket_transport.dart @@ -65,12 +65,12 @@ class TSocketTransport extends TBufferedTransport { if (isListening) { _setReadBuffer(_consumeWriteBuffer()); } else { - List result = await socket.send(_consumeWriteBuffer()); + Uint8List result = await socket.send(_consumeWriteBuffer()); _setReadBuffer(result); } } - void _onMessage(List message) { + void _onMessage(Uint8List message) { writeAll(message); _setReadBuffer(_consumeWriteBuffer()); } diff --git a/lib/dart/lib/src/transport/t_transport.dart b/lib/dart/lib/src/transport/t_transport.dart index 863205a8840..404c4b73276 100644 --- a/lib/dart/lib/src/transport/t_transport.dart +++ b/lib/dart/lib/src/transport/t_transport.dart @@ -32,13 +32,13 @@ abstract class TTransport { /// Reads up to [length] bytes into [buffer], starting at [offset]. /// Returns the number of bytes actually read. /// Throws [TTransportError] if there was an error reading data - int read(List buffer, int offset, int length); + int read(Uint8List buffer, int offset, int length); /// Guarantees that all of [length] bytes are actually read off the transport. /// Returns the number of bytes actually read, which must be equal to /// [length]. /// Throws [TTransportError] if there was an error reading data - int readAll(List buffer, int offset, int length) { + int readAll(Uint8List buffer, int offset, int length) { int got = 0; int ret = 0; while (got < length) { @@ -56,13 +56,13 @@ abstract class TTransport { /// Writes the [bytes] to the output. /// Throws [TTransportError] if there was an error writing data - void writeAll(List buffer) { + void writeAll(Uint8List buffer) { write(buffer, 0, buffer.length); } /// Writes up to [len] bytes from the buffer. /// Throws [TTransportError] if there was an error writing data - void write(List buffer, int offset, int length); + void write(Uint8List buffer, int offset, int length); /// Flush any pending data out of a transport buffer. /// Throws [TTransportError] if there was an error writing out data. diff --git a/tutorial/dart/client/web/client.dart b/tutorial/dart/client/web/client.dart index bfb5be032be..8c225cf267a 100644 --- a/tutorial/dart/client/web/client.dart +++ b/tutorial/dart/client/web/client.dart @@ -75,7 +75,6 @@ class CalculatorUI { ..text = "PING" ..onClick.listen(_onPingClick); output.append(pingButton); - output.append(new BRElement()); } void _onPingClick(MouseEvent e) { @@ -85,8 +84,6 @@ class CalculatorUI { } void _buildAddComponent() { - output.append(new BRElement()); - output.append(new HRElement()); output.append(new HeadingElement.h3()..text = "Add"); InputElement num1 = new InputElement() ..id = "add1" @@ -117,7 +114,6 @@ class CalculatorUI { ..style.fontSize = "14px" ..style.marginLeft = "10px"; output.append(result); - output.append(new BRElement()); } void _onAddClick(MouseEvent e) { @@ -135,8 +131,6 @@ class CalculatorUI { } void _buildCalculatorComponent() { - output.append(new BRElement()); - output.append(new HRElement()); output.append(new HeadingElement.h3()..text = "Calculator"); InputElement num1 = new InputElement() ..id = "calc1" @@ -211,7 +205,6 @@ class CalculatorUI { ..style.width = "100px" ..style.marginLeft = "10px"; output.append(comment); - output.append(new BRElement()); } void _onCalcClick(MouseEvent e) { @@ -224,20 +217,21 @@ class CalculatorUI { SelectElement logId = querySelector("#logId"); InputElement comment = querySelector("#comment"); + int logIdValue = int.parse(logId.value); + logId.value = (logIdValue + 1).toString(); + Work work = new Work(); work.num1 = int.parse(num1.value); work.num2 = int.parse(num2.value); work.op = int.parse(op.options[op.selectedIndex].value); work.comment = comment.value; - _calculatorClient.calculate(int.parse(logId.value), work).then((int n) { + _calculatorClient.calculate(logIdValue, work).then((int n) { result.text = "$n"; }); } void _buildGetStructComponent() { - output.append(new BRElement()); - output.append(new HRElement()); output.append(new HeadingElement.h3()..text = "Get Struct"); LabelElement logIdLabel = new LabelElement() ..text = "Struct Key:" @@ -266,7 +260,6 @@ class CalculatorUI { ..style.height = "50px" ..style.marginLeft = "10px"; output.append(result); - output.append(new BRElement()); } void _onGetStructClick(MouseEvent e) { diff --git a/tutorial/dart/client/web/styles.css b/tutorial/dart/client/web/styles.css index a39af230fa3..6b3683804c7 100644 --- a/tutorial/dart/client/web/styles.css +++ b/tutorial/dart/client/web/styles.css @@ -7,3 +7,9 @@ html, body { padding: 10px; font-family: 'Roboto', sans-serif; } + +h3 { + border-bottom: solid; + border-width: thin; + padding-top: 20px; +} diff --git a/tutorial/dart/server/bin/main.dart b/tutorial/dart/server/bin/main.dart index d78f60e6f64..0fe7da2bce6 100644 --- a/tutorial/dart/server/bin/main.dart +++ b/tutorial/dart/server/bin/main.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'dart:typed_data' show Uint8List; import 'package:thrift/thrift.dart'; import 'package:thrift/thrift_console.dart'; @@ -52,7 +53,7 @@ Future _initWebSocket(HttpRequest request) async { socket.onMessage.listen(_onMessage); } -Future _onMessage(List data) async { +Future _onMessage(Uint8List data) async { _processor.process(_inputProtocol, _outputProtocol); } From 4c74741fcc0e679943a4129ea9f6dbeecef8f488 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Fri, 11 Sep 2015 15:50:41 -0500 Subject: [PATCH 21/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Complete with error when a socket is closed. Minor code cleanup. --- lib/dart/lib/src/browser/t_web_socket.dart | 11 ++++++++++- lib/dart/lib/src/console/t_web_socket.dart | 5 +++++ lib/dart/lib/src/protocol/t_json_protocol.dart | 4 +--- lib/dart/lib/src/transport/t_http_transport.dart | 3 ++- lib/dart/lib/src/transport/t_socket_transport.dart | 2 +- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/dart/lib/src/browser/t_web_socket.dart b/lib/dart/lib/src/browser/t_web_socket.dart index 50e78446ca4..6da8b21aa53 100644 --- a/lib/dart/lib/src/browser/t_web_socket.dart +++ b/lib/dart/lib/src/browser/t_web_socket.dart @@ -102,7 +102,15 @@ class TWebSocket implements TSocket { void _onClose(CloseEvent event) { _socket = null; + + for (var completer in _completers) { + completer.completeError(new StateError("The socket has closed")); + } _completers.clear(); + + for (var request in _requests) { + request.completer.completeError(new StateError("The socket has closed")); + } _requests.clear(); _onStateController.add(TSocketState.CLOSED); @@ -112,7 +120,8 @@ class TWebSocket implements TSocket { Uint8List data; try { - data = new Uint8List.fromList(CryptoUtils.base64StringToBytes(event.data)); + data = + new Uint8List.fromList(CryptoUtils.base64StringToBytes(event.data)); } on FormatException catch (_) { _onErrorController .add(new UnsupportedError("Expected a Base 64 encoded string.")); diff --git a/lib/dart/lib/src/console/t_web_socket.dart b/lib/dart/lib/src/console/t_web_socket.dart index 3d867ba0086..2b7eb6e4618 100644 --- a/lib/dart/lib/src/console/t_web_socket.dart +++ b/lib/dart/lib/src/console/t_web_socket.dart @@ -67,6 +67,11 @@ class TWebSocket implements TSocket { _socket = null; } + for (var completer in _completers) { + completer.completeError(new StateError("The socket has closed")); + } + _completers.clear(); + _onStateController.add(TSocketState.CLOSED); } diff --git a/lib/dart/lib/src/protocol/t_json_protocol.dart b/lib/dart/lib/src/protocol/t_json_protocol.dart index 42be174e40e..31ba0fa9493 100644 --- a/lib/dart/lib/src/protocol/t_json_protocol.dart +++ b/lib/dart/lib/src/protocol/t_json_protocol.dart @@ -73,9 +73,7 @@ class TJsonProtocol extends TProtocol { } } - int _hexChar(int byte) { - return byte.toRadixString(16).codeUnitAt(0); - } + int _hexChar(int byte) => byte.toRadixString(16).codeUnitAt(0); /// write diff --git a/lib/dart/lib/src/transport/t_http_transport.dart b/lib/dart/lib/src/transport/t_http_transport.dart index 6b7609de15a..401ae0da050 100644 --- a/lib/dart/lib/src/transport/t_http_transport.dart +++ b/lib/dart/lib/src/transport/t_http_transport.dart @@ -50,7 +50,8 @@ class THttpClientTransport extends TBufferedTransport { Uint8List data; try { - data = new Uint8List.fromList(CryptoUtils.base64StringToBytes(response.body)); + data = new Uint8List.fromList( + CryptoUtils.base64StringToBytes(response.body)); } on FormatException catch (_) { throw new TProtocolError(TProtocolErrorType.INVALID_DATA, "Expected a Base 64 encoded string."); diff --git a/lib/dart/lib/src/transport/t_socket_transport.dart b/lib/dart/lib/src/transport/t_socket_transport.dart index 9cfc22a833d..320a7d15974 100644 --- a/lib/dart/lib/src/transport/t_socket_transport.dart +++ b/lib/dart/lib/src/transport/t_socket_transport.dart @@ -39,7 +39,7 @@ class TSocketTransport extends TBufferedTransport { TSocketTransport(this.socket, {isListening: false}) : this.isListening = isListening { if (socket == null) { - throw new ArgumentError.notNull("socket"); + throw new ArgumentError.notNull('socket'); } socket.onError.listen((String e) => log.warning(e)); From 1cbb3eb44f5219818c8e9229f0fe26a78a376d3c Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Fri, 11 Sep 2015 18:22:55 -0500 Subject: [PATCH 22/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Writing unit tests. A little code cleanup. --- lib/dart/lib/src/browser/t_web_socket.dart | 10 +- lib/dart/lib/src/console/t_web_socket.dart | 6 +- .../lib/src/protocol/t_json_protocol.dart | 26 ++-- .../test/protocol/t_json_protocol_test.dart | 83 ++++++++++ lib/dart/test/t_application_error_test.dart | 29 ++++ .../transport/t_socket_transport_test.dart | 145 ++++++++++++++++++ 6 files changed, 278 insertions(+), 21 deletions(-) create mode 100644 lib/dart/test/protocol/t_json_protocol_test.dart create mode 100644 lib/dart/test/t_application_error_test.dart create mode 100644 lib/dart/test/transport/t_socket_transport_test.dart diff --git a/lib/dart/lib/src/browser/t_web_socket.dart b/lib/dart/lib/src/browser/t_web_socket.dart index 6da8b21aa53..aa6431910e9 100644 --- a/lib/dart/lib/src/browser/t_web_socket.dart +++ b/lib/dart/lib/src/browser/t_web_socket.dart @@ -48,7 +48,7 @@ class TWebSocket implements TSocket { _onErrorController = new StreamController.broadcast(), _onMessageController = new StreamController.broadcast() { if (url == null || !url.hasAuthority || !url.hasPort) { - throw new ArgumentError("Invalid url"); + throw new ArgumentError('Invalid url'); } } @@ -62,7 +62,7 @@ class TWebSocket implements TSocket { Future open() async { if (!isClosed) { throw new TTransportError( - TTransportErrorType.ALREADY_OPEN, "Socket already connected"); + TTransportErrorType.ALREADY_OPEN, 'Socket already connected'); } _socket = new WebSocket(url.toString()); @@ -104,12 +104,12 @@ class TWebSocket implements TSocket { _socket = null; for (var completer in _completers) { - completer.completeError(new StateError("The socket has closed")); + completer.completeError(new StateError('The socket has closed')); } _completers.clear(); for (var request in _requests) { - request.completer.completeError(new StateError("The socket has closed")); + request.completer.completeError(new StateError('The socket has closed')); } _requests.clear(); @@ -124,7 +124,7 @@ class TWebSocket implements TSocket { new Uint8List.fromList(CryptoUtils.base64StringToBytes(event.data)); } on FormatException catch (_) { _onErrorController - .add(new UnsupportedError("Expected a Base 64 encoded string.")); + .add(new UnsupportedError('Expected a Base 64 encoded string.')); } if (!_completers.isEmpty) { diff --git a/lib/dart/lib/src/console/t_web_socket.dart b/lib/dart/lib/src/console/t_web_socket.dart index 2b7eb6e4618..8f54dfa2309 100644 --- a/lib/dart/lib/src/console/t_web_socket.dart +++ b/lib/dart/lib/src/console/t_web_socket.dart @@ -68,7 +68,7 @@ class TWebSocket implements TSocket { } for (var completer in _completers) { - completer.completeError(new StateError("The socket has closed")); + completer.completeError(new StateError('The socket has closed')); } _completers.clear(); @@ -98,7 +98,7 @@ class TWebSocket implements TSocket { data = new Uint8List.fromList(CryptoUtils.base64StringToBytes(message)); } on FormatException catch (_) { _onErrorController - .add(new UnsupportedError("Expected a Base 64 encoded string.")); + .add(new UnsupportedError('Expected a Base 64 encoded string.')); } if (!_completers.isEmpty) { @@ -110,6 +110,6 @@ class TWebSocket implements TSocket { void _onError(Object error) { close(); - _onErrorController.add("$error"); + _onErrorController.add('$error'); } } diff --git a/lib/dart/lib/src/protocol/t_json_protocol.dart b/lib/dart/lib/src/protocol/t_json_protocol.dart index 31ba0fa9493..8c0355124a5 100644 --- a/lib/dart/lib/src/protocol/t_json_protocol.dart +++ b/lib/dart/lib/src/protocol/t_json_protocol.dart @@ -50,7 +50,7 @@ class TJsonProtocol extends TProtocol { } /// Read a byte that must match [char]; otherwise throw a [TProtocolError]. - void readJsonSyntaxChar(String char) { + void _readJsonSyntaxChar(String char) { int charByte = char.codeUnitAt(0); int byte = _reader.read(); if (byte != charByte) { @@ -281,7 +281,7 @@ class TJsonProtocol extends TProtocol { _context.read(); } - readJsonSyntaxChar(_Constants.QUOTE); + _readJsonSyntaxChar(_Constants.QUOTE); while (true) { int byte = _reader.read(); if (byte == _Constants.QUOTE.codeUnitAt(0)) { @@ -310,8 +310,8 @@ class TJsonProtocol extends TProtocol { } // it's \u00XX - readJsonSyntaxChar(_Constants.HEX_0); - readJsonSyntaxChar(_Constants.HEX_0); + _readJsonSyntaxChar(_Constants.HEX_0); + _readJsonSyntaxChar(_Constants.HEX_0); transport.readAll(_tempBuffer, 0, 2); byte = _hexVal(_tempBuffer[0]) << 4 + _hexVal(_tempBuffer[1]); bytes.add(byte); @@ -335,11 +335,11 @@ class TJsonProtocol extends TProtocol { _context.read(); if (_context.escapeNumbers) { - readJsonSyntaxChar(_Constants.QUOTE); + _readJsonSyntaxChar(_Constants.QUOTE); } String str = _readJsonNumericChars(); if (_context.escapeNumbers) { - readJsonSyntaxChar(_Constants.QUOTE); + _readJsonSyntaxChar(_Constants.QUOTE); } try { @@ -367,7 +367,7 @@ class TJsonProtocol extends TProtocol { } else { if (_context.escapeNumbers) { // This will throw - we should have had a quote if escapeNumbers == true - readJsonSyntaxChar(_Constants.QUOTE); + _readJsonSyntaxChar(_Constants.QUOTE); } return double.parse(_readJsonNumericChars(), (_) { throw new TProtocolError(TProtocolErrorType.INVALID_DATA, @@ -386,23 +386,23 @@ class TJsonProtocol extends TProtocol { void _readJsonObjectStart() { _context.read(); - readJsonSyntaxChar(_Constants.LBRACE); + _readJsonSyntaxChar(_Constants.LBRACE); _pushContext(new _PairContext(this)); } void _readJsonObjectEnd() { - readJsonSyntaxChar(_Constants.RBRACE); + _readJsonSyntaxChar(_Constants.RBRACE); _popContext(); } void _readJsonArrayStart() { _context.read(); - readJsonSyntaxChar(_Constants.LBRACKET); + _readJsonSyntaxChar(_Constants.LBRACKET); _pushContext(new _ListContext(this)); } void _readJsonArrayEnd() { - readJsonSyntaxChar(_Constants.RBRACKET); + _readJsonSyntaxChar(_Constants.RBRACKET); _popContext(); } @@ -696,7 +696,7 @@ class _ListContext extends _BaseContext { if (_first) { _first = false; } else { - protocol.readJsonSyntaxChar(_Constants.COMMA); + protocol._readJsonSyntaxChar(_Constants.COMMA); } } } @@ -724,7 +724,7 @@ class _PairContext extends _BaseContext { _first = false; _colon = true; } else { - protocol.readJsonSyntaxChar(symbol); + protocol._readJsonSyntaxChar(symbol); _colon = !_colon; } } diff --git a/lib/dart/test/protocol/t_json_protocol_test.dart b/lib/dart/test/protocol/t_json_protocol_test.dart new file mode 100644 index 00000000000..2e705561d1e --- /dev/null +++ b/lib/dart/test/protocol/t_json_protocol_test.dart @@ -0,0 +1,83 @@ +library thrift.test.transport.t_json_protocol_test; + +import 'package:test/test.dart'; +import 'package:thrift/thrift.dart'; + +void main() { + + TJsonProtocol protocol; + TMessage message; + + setUp(() { + protocol = new TJsonProtocol(new TBufferedTransport()); + + message = new TMessage('my message', TMessageType.ONEWAY, 123); + protocol.writeMessageBegin(message); + }); + + test('Test message', () async { + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + var subject = protocol.readMessageBegin(); + + expect(subject.name, message.name); + expect(subject.type, message.type); + expect(subject.seqid, message.seqid); + }); + + test('Test struct', () async { + var struct = new TStruct(); + + protocol.writeStructBegin(struct); + protocol.writeStructEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var subject = protocol.readStructBegin(); + + // struct name is not serialized, see C# version for reference + expect(subject, isNotNull); + }); + + test('Test field', () async { + var field = new TField('my field', TType.MAP, 123); + + protocol.writeFieldBegin(field); + protocol.writeFieldEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var subject = protocol.readFieldBegin(); + + // struct name is not serialized, see C# version for reference + expect(subject.type, field.type); + expect(subject.id, field.id); + }); + + test('Test map', () async { + var map = new TMap(TType.STRING, TType.STRUCT, 123); + + protocol.writeMapBegin(map); + protocol.writeMapEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var subject = protocol.readMapBegin(); + + // struct name is not serialized, see C# version for reference + expect(subject.keyType, map.keyType); + expect(subject.valueType, map.valueType); + expect(subject.length, map.length); + }); + + + +} diff --git a/lib/dart/test/t_application_error_test.dart b/lib/dart/test/t_application_error_test.dart new file mode 100644 index 00000000000..0563d794762 --- /dev/null +++ b/lib/dart/test/t_application_error_test.dart @@ -0,0 +1,29 @@ +library thrift.test.t_application_error_test; + +import 'package:test/test.dart'; +import 'package:thrift/thrift.dart'; + +void main() { + TProtocol protocol; + + setUp(() { + protocol = new TBinaryProtocol(new TBufferedTransport()); + }); + + test('Write and read an application error', () { + var expectedType = TApplicationErrorType.INTERNAL_ERROR; + var expectedMessage = 'test error message'; + + TApplicationError error = new TApplicationError(expectedType, expectedMessage); + error.write(protocol); + + protocol.transport.flush(); + + TApplicationError subject = TApplicationError.read(protocol); + + expect(subject, isNotNull); + expect(subject.type, expectedType); + expect(subject.message, expectedMessage); + }); + +} diff --git a/lib/dart/test/transport/t_socket_transport_test.dart b/lib/dart/test/transport/t_socket_transport_test.dart new file mode 100644 index 00000000000..f67a3e23360 --- /dev/null +++ b/lib/dart/test/transport/t_socket_transport_test.dart @@ -0,0 +1,145 @@ +library thrift.test.transport.t_socket_transport_test; + +import 'dart:async'; +import 'dart:convert' show Utf8Codec; +import 'dart:typed_data' show Uint8List; + +import 'package:test/test.dart'; +import 'package:thrift/thrift.dart'; + +void main() { + + const utf8Codec = const Utf8Codec(); + + final expectedText = 'my test data'; + final expectedBytes = new Uint8List.fromList(utf8Codec.encode(expectedText)); + + test('Test transport listens to socket', () async { + var socket = new FakeSocket(isServer: true); + await socket.open(); + expect(socket.isOpen, isTrue); + + var transport = new TSocketTransport(socket, isListening: true); + expect(transport.hasReadData, isFalse); + + socket.receiveFakeMessage(expectedBytes); + + // allow microtask events to finish + await new Future.value(); + + expect(transport.hasReadData, isTrue); + + var buffer = new Uint8List(expectedBytes.length); + transport.readAll(buffer, 0, expectedBytes.length); + + var bufferText = utf8Codec.decode(buffer); + + expect(bufferText, expectedText); + }); + + test('Test transport does not listen to socket', () async { + var socket = new FakeSocket(); + await socket.open(); + + var transport = new TSocketTransport(socket, isListening: false); + + socket.receiveFakeMessage(expectedBytes); + + // allow microtask events to finish + await new Future.value(); + + expect(transport.hasReadData, isFalse); + }); + + test('Test sending data over transport', () async { + var socket = new FakeSocket(isServer: true); + await socket.open(); + + var transport = new TSocketTransport(socket, isListening: false); + + transport.writeAll(expectedBytes); + expect(socket.sendPayload, isNull); + + transport.flush(); + + // allow microtask events to finish + await new Future.value(); + + expect(socket.sendPayload, isNotNull); + expect(utf8Codec.decode(socket.sendPayload), expectedText); + }); + +} + + +class FakeSocket extends TSocket { + + final StreamController _onStateController; + Stream get onState => _onStateController.stream; + + final StreamController _onErrorController; + Stream get onError => _onErrorController.stream; + + final StreamController _onMessageController; + Stream get onMessage => _onMessageController.stream; + + final List> _completers = []; + + FakeSocket({this.isServer: false}) + : _onStateController = new StreamController.broadcast(), + _onErrorController = new StreamController.broadcast(), + _onMessageController = new StreamController.broadcast(); + + final bool isServer; + + bool _isOpen; + + bool get isOpen => _isOpen; + + bool get isClosed => !isOpen; + + Future open() async { + _isOpen = true; + _onStateController.add(TSocketState.OPEN); + } + + Future close() async { + _isOpen = false; + for (var completer in _completers) { + completer.completeError(new StateError('The socked has closed')); + } + _onStateController.add(TSocketState.CLOSED); + } + + Uint8List _sendPayload; + + Uint8List get sendPayload => _sendPayload; + + Future send(Uint8List data) { + if (!isOpen) throw new StateError("The socket is not open"); + + Future result; + if (isServer) { + result = new Future.value(); + } else { + Completer completer = new Completer(); + _completers.add(completer); + result = completer.future; + } + + _sendPayload = data; + + return result; + } + + void receiveFakeMessage(Uint8List message) { + if (!isOpen) throw new StateError("The socket is not open"); + + if (!_completers.isEmpty) { + _completers.removeAt(0).complete(message); + } + + _onMessageController.add(message); + } + +} From d97bcc1b63dc80a8415e102d484cd4cbfb1df16a Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Fri, 11 Sep 2015 21:23:30 -0500 Subject: [PATCH 23/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 JSON protocol unit tests. Changed binary type to Uint8List. Cleaned up tutorial shell script. --- compiler/cpp/src/generate/t_dart_generator.cc | 4 +- .../lib/src/protocol/t_binary_protocol.dart | 17 +- .../lib/src/protocol/t_json_protocol.dart | 8 +- lib/dart/lib/src/protocol/t_protocol.dart | 4 +- .../src/protocol/t_protocol_decorator.dart | 4 +- .../test/protocol/t_json_protocol_test.dart | 181 ++++++++++++++++-- tutorial/dart/build.sh | 40 +++- 7 files changed, 213 insertions(+), 45 deletions(-) diff --git a/compiler/cpp/src/generate/t_dart_generator.cc b/compiler/cpp/src/generate/t_dart_generator.cc index 360538d3b9e..285b5c8c3c0 100644 --- a/compiler/cpp/src/generate/t_dart_generator.cc +++ b/compiler/cpp/src/generate/t_dart_generator.cc @@ -302,7 +302,7 @@ string t_dart_generator::service_imports() { * @return List of imports necessary for thrift */ string t_dart_generator::dart_thrift_imports() { - string imports = "import 'dart:typed_data' show ByteData;" + endl + + string imports = "import 'dart:typed_data' show Uint8List;" + endl + "import 'package:thrift/thrift.dart';" + endl + "import 'package:" + library_name_ + "/" + library_name_ + ".dart';" + endl; @@ -2084,7 +2084,7 @@ string t_dart_generator::base_type_name(t_base_type* type, bool in_container) { return "void"; case t_base_type::TYPE_STRING: if (type->is_binary()) { - return "ByteData"; + return "Uint8List"; } else { return "String"; } diff --git a/lib/dart/lib/src/protocol/t_binary_protocol.dart b/lib/dart/lib/src/protocol/t_binary_protocol.dart index f1e309e05d3..ac8e0a42859 100644 --- a/lib/dart/lib/src/protocol/t_binary_protocol.dart +++ b/lib/dart/lib/src/protocol/t_binary_protocol.dart @@ -126,9 +126,10 @@ class TBinaryProtocol extends TProtocol { transport.write(_doubleOut.buffer.asUint8List(), 0, 8); } - void writeBinary(ByteData bytes) { - writeI32(bytes.lengthInBytes); - transport.write(bytes.buffer.asUint8List(), 0, bytes.lengthInBytes); + void writeBinary(Uint8List bytes) { + var length = bytes.length; + writeI32(length); + transport.write(bytes, 0, length); } /// read @@ -248,10 +249,10 @@ class TBinaryProtocol extends TProtocol { return _utf8Codec.decode(stringIn); } - ByteData readBinary() { - int size = readI32(); - Uint8List binaryIn = new Uint8List(size); - transport.readAll(binaryIn, 0, size); - return binaryIn.buffer.asByteData(); + Uint8List readBinary() { + int length = readI32(); + Uint8List binaryIn = new Uint8List(length); + transport.readAll(binaryIn, 0, length); + return binaryIn; } } diff --git a/lib/dart/lib/src/protocol/t_json_protocol.dart b/lib/dart/lib/src/protocol/t_json_protocol.dart index 8c0355124a5..73cf8ee2d8a 100644 --- a/lib/dart/lib/src/protocol/t_json_protocol.dart +++ b/lib/dart/lib/src/protocol/t_json_protocol.dart @@ -268,8 +268,8 @@ class TJsonProtocol extends TProtocol { _writeJsonString(utf8Codec.encode(s)); } - void writeBinary(ByteData bytes) { - _writeJsonBase64(bytes.buffer.asUint8List()); + void writeBinary(Uint8List bytes) { + _writeJsonBase64(bytes); } /// read @@ -521,8 +521,8 @@ class TJsonProtocol extends TProtocol { return utf8Codec.decode(_readJsonString()); } - ByteData readBinary() { - return new Uint8List.fromList(_readJsonBase64()).buffer.asByteData(); + Uint8List readBinary() { + return new Uint8List.fromList(_readJsonBase64()); } } diff --git a/lib/dart/lib/src/protocol/t_protocol.dart b/lib/dart/lib/src/protocol/t_protocol.dart index d2023f09b81..f49c0321d76 100644 --- a/lib/dart/lib/src/protocol/t_protocol.dart +++ b/lib/dart/lib/src/protocol/t_protocol.dart @@ -56,7 +56,7 @@ abstract class TProtocol { void writeString(String str); - void writeBinary(ByteData bytes); + void writeBinary(Uint8List bytes); /// Read TMessage readMessageBegin(); @@ -91,5 +91,5 @@ abstract class TProtocol { String readString(); - ByteData readBinary(); + Uint8List readBinary(); } diff --git a/lib/dart/lib/src/protocol/t_protocol_decorator.dart b/lib/dart/lib/src/protocol/t_protocol_decorator.dart index 457ccec161e..9cd02f64511 100644 --- a/lib/dart/lib/src/protocol/t_protocol_decorator.dart +++ b/lib/dart/lib/src/protocol/t_protocol_decorator.dart @@ -109,7 +109,7 @@ class TProtocolDecorator extends TProtocol { _protocol.writeString(str); } - void writeBinary(ByteData bytes) { + void writeBinary(Uint8List bytes) { _protocol.writeBinary(bytes); } @@ -146,5 +146,5 @@ class TProtocolDecorator extends TProtocol { String readString() => _protocol.readString(); - ByteData readBinary() => _protocol.readBinary(); + Uint8List readBinary() => _protocol.readBinary(); } diff --git a/lib/dart/test/protocol/t_json_protocol_test.dart b/lib/dart/test/protocol/t_json_protocol_test.dart index 2e705561d1e..202af1f75a4 100644 --- a/lib/dart/test/protocol/t_json_protocol_test.dart +++ b/lib/dart/test/protocol/t_json_protocol_test.dart @@ -1,5 +1,7 @@ library thrift.test.transport.t_json_protocol_test; +import 'dart:typed_data' show Uint8List; + import 'package:test/test.dart'; import 'package:thrift/thrift.dart'; @@ -28,56 +30,199 @@ void main() { }); test('Test struct', () async { - var struct = new TStruct(); + var input = new TStruct(); - protocol.writeStructBegin(struct); + protocol.writeStructBegin(input); protocol.writeStructEnd(); protocol.writeMessageEnd(); await protocol.transport.flush(); protocol.readMessageBegin(); - var subject = protocol.readStructBegin(); + var output = protocol.readStructBegin(); - // struct name is not serialized, see C# version for reference - expect(subject, isNotNull); + // name is not serialized, see C# version for reference + expect(output, isNotNull); }); test('Test field', () async { - var field = new TField('my field', TType.MAP, 123); + var input = new TField('my field', TType.MAP, 123); - protocol.writeFieldBegin(field); + protocol.writeFieldBegin(input); protocol.writeFieldEnd(); protocol.writeMessageEnd(); await protocol.transport.flush(); protocol.readMessageBegin(); - var subject = protocol.readFieldBegin(); + var output = protocol.readFieldBegin(); - // struct name is not serialized, see C# version for reference - expect(subject.type, field.type); - expect(subject.id, field.id); + // name is not serialized, see C# version for reference + expect(output.type, input.type); + expect(output.id, input.id); }); test('Test map', () async { - var map = new TMap(TType.STRING, TType.STRUCT, 123); + var input = new TMap(TType.STRING, TType.STRUCT, 123); - protocol.writeMapBegin(map); + protocol.writeMapBegin(input); protocol.writeMapEnd(); protocol.writeMessageEnd(); await protocol.transport.flush(); protocol.readMessageBegin(); - var subject = protocol.readMapBegin(); + var output = protocol.readMapBegin(); + + expect(output.keyType, input.keyType); + expect(output.valueType, input.valueType); + expect(output.length, input.length); + }); + + test('Test list', () async { + var input = new TList(TType.STRING, 123); + + protocol.writeListBegin(input); + protocol.writeListEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readListBegin(); - // struct name is not serialized, see C# version for reference - expect(subject.keyType, map.keyType); - expect(subject.valueType, map.valueType); - expect(subject.length, map.length); + expect(output.elementType, input.elementType); + expect(output.length, input.length); }); + test('Test set', () async { + var input = new TSet(TType.STRING, 123); + + protocol.writeSetBegin(input); + protocol.writeSetEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readListBegin(); + + expect(output.elementType, input.elementType); + expect(output.length, input.length); + }); + + test('Test bool', () async { + var input = true; + + protocol.writeBool(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readBool(); + + expect(output, input); + }); + + test('Test byte', () async { + var input = 64; + + protocol.writeByte(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readByte(); + + expect(output, input); + }); + + test('Test I16', () async { + var input = 32767; + + protocol.writeI16(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readI32(); + + expect(output, input); + }); + test('Test I32', () async { + var input = 2147483647; + + protocol.writeI32(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readI32(); + + expect(output, input); + }); + + test('Test I64', () async { + var input = 9223372036854775807; + + protocol.writeI64(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readI64(); + + expect(output, input); + }); + + test('Test double', () async { + var input = 3.1415926; + + protocol.writeDouble(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readDouble(); + + expect(output, input); + }); + + test('Test string', () async { + var input = 'There are only two hard things in computer science: ' + 'cache invalidation, naming things, and off-by-one errors.'; + + protocol.writeString(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readString(); + + expect(output, input); + }); + + test('Test binary', () async { + var input = new Uint8List.fromList(new List.filled(100, 123)); + + protocol.writeBinary(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readBinary(); + + expect(output.length, input.length); + expect(output.every((i) => i == 123), isTrue); + }); } diff --git a/tutorial/dart/build.sh b/tutorial/dart/build.sh index 4eed80b5f0f..505385e4c07 100755 --- a/tutorial/dart/build.sh +++ b/tutorial/dart/build.sh @@ -1,11 +1,33 @@ #!/bin/sh -rm -r gen-dart || true && \ -thrift --gen "dart:gen_server" "../shared.thrift" && \ - cd gen-dart/shared && pub get && cd ../.. && \ -thrift --gen "dart:gen_server" "../tutorial.thrift" && \ - cd gen-dart/tutorial && pub get && cd ../.. && \ -cd client && pub get && cd .. && \ -cd server && pub get && cd .. && \ -dartfmt -w gen-dart && \ -echo "Enjoy the Dart tutorial!" +set -e; +rm -r gen-dart || true; + +thrift --gen "dart:gen_server" "../shared.thrift"; +cd gen-dart/shared; +pub get; +cd ../..; + +thrift --gen "dart:gen_server" "../tutorial.thrift"; +cd gen-dart/tutorial; +pub get; +cd ../..; + +cd client; +pub get; +cd ..; + +cd server; +pub get; +cd ..; + +dartfmt -w gen-dart; + +echo "\nEnjoy the Dart tutorial!"; +echo "\nTo run the server:"; +echo "> dart server/bin/main.dart"; +echo "\nTo run the client:"; +echo "# Serve the app from the client directory and view in a browser"; +echo "> cd client;"; +echo "> pub serve;"; +echo ""; From e236093ee74ff44742fcfdb4971e135f70fa672c Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Fri, 11 Sep 2015 23:00:00 -0500 Subject: [PATCH 24/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Protocol and transport unit tests. --- .../test/protocol/t_json_protocol_test.dart | 228 ----------------- lib/dart/test/protocol/t_protocol_test.dart | 242 ++++++++++++++++++ .../test/transport/t_http_transport_test.dart | 103 ++++++++ .../transport/t_socket_transport_test.dart | 1 - 4 files changed, 345 insertions(+), 229 deletions(-) delete mode 100644 lib/dart/test/protocol/t_json_protocol_test.dart create mode 100644 lib/dart/test/protocol/t_protocol_test.dart create mode 100644 lib/dart/test/transport/t_http_transport_test.dart diff --git a/lib/dart/test/protocol/t_json_protocol_test.dart b/lib/dart/test/protocol/t_json_protocol_test.dart deleted file mode 100644 index 202af1f75a4..00000000000 --- a/lib/dart/test/protocol/t_json_protocol_test.dart +++ /dev/null @@ -1,228 +0,0 @@ -library thrift.test.transport.t_json_protocol_test; - -import 'dart:typed_data' show Uint8List; - -import 'package:test/test.dart'; -import 'package:thrift/thrift.dart'; - -void main() { - - TJsonProtocol protocol; - TMessage message; - - setUp(() { - protocol = new TJsonProtocol(new TBufferedTransport()); - - message = new TMessage('my message', TMessageType.ONEWAY, 123); - protocol.writeMessageBegin(message); - }); - - test('Test message', () async { - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - var subject = protocol.readMessageBegin(); - - expect(subject.name, message.name); - expect(subject.type, message.type); - expect(subject.seqid, message.seqid); - }); - - test('Test struct', () async { - var input = new TStruct(); - - protocol.writeStructBegin(input); - protocol.writeStructEnd(); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readStructBegin(); - - // name is not serialized, see C# version for reference - expect(output, isNotNull); - }); - - test('Test field', () async { - var input = new TField('my field', TType.MAP, 123); - - protocol.writeFieldBegin(input); - protocol.writeFieldEnd(); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readFieldBegin(); - - // name is not serialized, see C# version for reference - expect(output.type, input.type); - expect(output.id, input.id); - }); - - test('Test map', () async { - var input = new TMap(TType.STRING, TType.STRUCT, 123); - - protocol.writeMapBegin(input); - protocol.writeMapEnd(); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readMapBegin(); - - expect(output.keyType, input.keyType); - expect(output.valueType, input.valueType); - expect(output.length, input.length); - }); - - test('Test list', () async { - var input = new TList(TType.STRING, 123); - - protocol.writeListBegin(input); - protocol.writeListEnd(); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readListBegin(); - - expect(output.elementType, input.elementType); - expect(output.length, input.length); - }); - - test('Test set', () async { - var input = new TSet(TType.STRING, 123); - - protocol.writeSetBegin(input); - protocol.writeSetEnd(); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readListBegin(); - - expect(output.elementType, input.elementType); - expect(output.length, input.length); - }); - - test('Test bool', () async { - var input = true; - - protocol.writeBool(input); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readBool(); - - expect(output, input); - }); - - test('Test byte', () async { - var input = 64; - - protocol.writeByte(input); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readByte(); - - expect(output, input); - }); - - test('Test I16', () async { - var input = 32767; - - protocol.writeI16(input); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readI32(); - - expect(output, input); - }); - - test('Test I32', () async { - var input = 2147483647; - - protocol.writeI32(input); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readI32(); - - expect(output, input); - }); - - test('Test I64', () async { - var input = 9223372036854775807; - - protocol.writeI64(input); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readI64(); - - expect(output, input); - }); - - test('Test double', () async { - var input = 3.1415926; - - protocol.writeDouble(input); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readDouble(); - - expect(output, input); - }); - - test('Test string', () async { - var input = 'There are only two hard things in computer science: ' - 'cache invalidation, naming things, and off-by-one errors.'; - - protocol.writeString(input); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readString(); - - expect(output, input); - }); - - test('Test binary', () async { - var input = new Uint8List.fromList(new List.filled(100, 123)); - - protocol.writeBinary(input); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readBinary(); - - expect(output.length, input.length); - expect(output.every((i) => i == 123), isTrue); - }); - -} diff --git a/lib/dart/test/protocol/t_protocol_test.dart b/lib/dart/test/protocol/t_protocol_test.dart new file mode 100644 index 00000000000..a9d9dce8b0f --- /dev/null +++ b/lib/dart/test/protocol/t_protocol_test.dart @@ -0,0 +1,242 @@ +library thrift.test.transport.t_json_protocol_test; + +import 'dart:typed_data' show Uint8List; + +import 'package:test/test.dart'; +import 'package:thrift/thrift.dart'; + +void main() { + + final message = new TMessage('my message', TMessageType.ONEWAY, 123); + + TProtocol protocol; + + var sharedTests = () { + test('Test message', () async { + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + var subject = protocol.readMessageBegin(); + + expect(subject.name, message.name); + expect(subject.type, message.type); + expect(subject.seqid, message.seqid); + }); + + test('Test struct', () async { + var input = new TStruct(); + + protocol.writeStructBegin(input); + protocol.writeStructEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readStructBegin(); + + // name is not serialized, see C# version for reference + expect(output, isNotNull); + }); + + test('Test field', () async { + var input = new TField('my field', TType.MAP, 123); + + protocol.writeFieldBegin(input); + protocol.writeFieldEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readFieldBegin(); + + // name is not serialized, see C# version for reference + expect(output.type, input.type); + expect(output.id, input.id); + }); + + test('Test map', () async { + var input = new TMap(TType.STRING, TType.STRUCT, 123); + + protocol.writeMapBegin(input); + protocol.writeMapEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readMapBegin(); + + expect(output.keyType, input.keyType); + expect(output.valueType, input.valueType); + expect(output.length, input.length); + }); + + test('Test list', () async { + var input = new TList(TType.STRING, 123); + + protocol.writeListBegin(input); + protocol.writeListEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readListBegin(); + + expect(output.elementType, input.elementType); + expect(output.length, input.length); + }); + + test('Test set', () async { + var input = new TSet(TType.STRING, 123); + + protocol.writeSetBegin(input); + protocol.writeSetEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readListBegin(); + + expect(output.elementType, input.elementType); + expect(output.length, input.length); + }); + + test('Test bool', () async { + var input = true; + + protocol.writeBool(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readBool(); + + expect(output, input); + }); + + test('Test byte', () async { + var input = 64; + + protocol.writeByte(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readByte(); + + expect(output, input); + }); + + test('Test I16', () async { + var input = 32767; + + protocol.writeI16(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readI16(); + + expect(output, input); + }); + + test('Test I32', () async { + var input = 2147483647; + + protocol.writeI32(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readI32(); + + expect(output, input); + }); + + test('Test I64', () async { + var input = 9223372036854775807; + + protocol.writeI64(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readI64(); + + expect(output, input); + }); + + test('Test double', () async { + var input = 3.1415926; + + protocol.writeDouble(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readDouble(); + + expect(output, input); + }); + + test('Test string', () async { + var input = 'There are only two hard things in computer science: ' + 'cache invalidation, naming things, and off-by-one errors.'; + + protocol.writeString(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readString(); + + expect(output, input); + }); + + test('Test binary', () async { + var input = new Uint8List.fromList(new List.filled(100, 123)); + + protocol.writeBinary(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readBinary(); + + expect(output.length, input.length); + expect(output.every((i) => i == 123), isTrue); + }); + }; + + group('JSON', () { + setUp(() { + protocol = new TJsonProtocol(new TBufferedTransport()); + protocol.writeMessageBegin(message); + }); + + group('shared tests', sharedTests); + }); + + group('binary', () { + setUp(() { + protocol = new TBinaryProtocol(new TBufferedTransport()); + protocol.writeMessageBegin(message); + }); + + group('shared tests', sharedTests); + }); + +} diff --git a/lib/dart/test/transport/t_http_transport_test.dart b/lib/dart/test/transport/t_http_transport_test.dart new file mode 100644 index 00000000000..32293c0966b --- /dev/null +++ b/lib/dart/test/transport/t_http_transport_test.dart @@ -0,0 +1,103 @@ +library thrift.test.transport.t_socket_transport_test; + +import 'dart:async'; +import 'dart:convert' show Encoding; +import 'dart:convert' show Utf8Codec; +import 'dart:typed_data' show Uint8List; + +import 'package:crypto/crypto.dart' show CryptoUtils; +import 'package:http/http.dart' show BaseRequest; +import 'package:http/http.dart' show Client; +import 'package:http/http.dart' show Response; +import 'package:http/http.dart' show StreamedResponse; +import 'package:test/test.dart'; +import 'package:thrift/thrift.dart'; + +void main() { + + const utf8Codec = const Utf8Codec(); + + FakeHttpClient client; + THttpClientTransport transport; + + setUp(() { + client = new FakeHttpClient(); + var config = new THttpConfig(Uri.parse("http://localhost"), {}); + transport = new THttpClientTransport(client, config); + }); + + test('Test transport sends body', () async { + var expectedText = "my request"; + transport.writeAll(utf8Codec.encode(expectedText)); + + expect(client.postRequest, isEmpty); + + await transport.flush(); + + expect(client.postRequest, isNotEmpty); + + var requestText = utf8Codec.decode( + CryptoUtils.base64StringToBytes(client.postRequest)); + expect(requestText, expectedText); + }); + + test('Test transport receives response', () async { + var expectedText = "my response"; + var expectedBytes = utf8Codec.encode(expectedText); + client.postResponse = CryptoUtils.bytesToBase64(expectedBytes); + + transport.writeAll(utf8Codec.encode("my request")); + expect(transport.hasReadData, isFalse); + + await transport.flush(); + + expect(transport.hasReadData, isTrue); + + var buffer = new Uint8List(expectedBytes.length); + transport.readAll(buffer, 0, expectedBytes.length); + + var bufferText = utf8Codec.decode(buffer); + expect(bufferText, expectedText); + }); + +} + + +class FakeHttpClient implements Client { + + String postResponse = ""; + String postRequest = ""; + + Future post(url, {Map headers, body, + Encoding encoding}) async { + postRequest = body; + return new Response(postResponse, 200); + } + + Future head(url, {Map headers}) => + throw new UnimplementedError(); + + Future get(url, {Map headers}) => + throw new UnimplementedError(); + + Future put(url, {Map headers, body, + Encoding encoding}) => throw new UnimplementedError(); + + Future patch(url, {Map headers, body, + Encoding encoding}) => throw new UnimplementedError(); + + Future delete(url, {Map headers}) => + throw new UnimplementedError(); + + Future read(url, {Map headers}) => + throw new UnimplementedError(); + + Future readBytes(url, {Map headers}) => + throw new UnimplementedError(); + + Future send(BaseRequest request) => + throw new UnimplementedError(); + + void close() => throw new UnimplementedError(); + +} diff --git a/lib/dart/test/transport/t_socket_transport_test.dart b/lib/dart/test/transport/t_socket_transport_test.dart index f67a3e23360..fe1a6da3c80 100644 --- a/lib/dart/test/transport/t_socket_transport_test.dart +++ b/lib/dart/test/transport/t_socket_transport_test.dart @@ -33,7 +33,6 @@ void main() { transport.readAll(buffer, 0, expectedBytes.length); var bufferText = utf8Codec.decode(buffer); - expect(bufferText, expectedText); }); From 8d74526a67a5d4fb583d8dc009eb33b5c7e728ad Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Mon, 14 Sep 2015 10:23:09 -0500 Subject: [PATCH 25/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Move completer logic from WebSocket implementations to transport. Split TSocketTransport into TClientSocketTransport and TServerSocketTransport to cleanup some conditional logic. --- lib/dart/lib/src/browser/t_web_socket.dart | 54 ++----- lib/dart/lib/src/console/t_web_socket.dart | 50 +------ lib/dart/lib/src/transport/t_socket.dart | 4 +- .../lib/src/transport/t_socket_transport.dart | 99 ++++++++++--- .../transport/t_socket_transport_test.dart | 137 +++++++++--------- tutorial/dart/client/web/client.dart | 2 +- tutorial/dart/server/bin/main.dart | 27 ++-- 7 files changed, 179 insertions(+), 194 deletions(-) diff --git a/lib/dart/lib/src/browser/t_web_socket.dart b/lib/dart/lib/src/browser/t_web_socket.dart index aa6431910e9..a868c69caf8 100644 --- a/lib/dart/lib/src/browser/t_web_socket.dart +++ b/lib/dart/lib/src/browser/t_web_socket.dart @@ -22,9 +22,7 @@ import 'dart:html' show CloseEvent; import 'dart:html' show Event; import 'dart:html' show MessageEvent; import 'dart:html' show WebSocket; -import 'dart:typed_data' show Uint8List; -import 'package:crypto/crypto.dart' show CryptoUtils; import 'package:thrift/thrift.dart'; /// A [TSocket] backed by a [WebSocket] from dart:html @@ -37,11 +35,10 @@ class TWebSocket implements TSocket { final StreamController _onErrorController; Stream get onError => _onErrorController.stream; - final StreamController _onMessageController; - Stream get onMessage => _onMessageController.stream; + final StreamController _onMessageController; + Stream get onMessage => _onMessageController.stream; - final List> _completers = []; - final List<_Request> _requests = []; + final List _requests = []; TWebSocket(this.url) : _onStateController = new StreamController.broadcast(), @@ -78,20 +75,15 @@ class TWebSocket implements TSocket { } } - Future send(Uint8List data) { - Completer completer = new Completer(); - - _requests.add(new _Request(data, completer)); + void send(String data) { + _requests.add(data); _sendRequests(); - - return completer.future; } void _sendRequests() { while (isOpen && _requests.isNotEmpty) { - _Request request = _requests.removeAt(0); - _completers.add(request.completer); - _socket.sendString(CryptoUtils.bytesToBase64(request.data)); + String data = _requests.removeAt(0); + _socket.sendString(data); } } @@ -103,13 +95,8 @@ class TWebSocket implements TSocket { void _onClose(CloseEvent event) { _socket = null; - for (var completer in _completers) { - completer.completeError(new StateError('The socket has closed')); - } - _completers.clear(); - - for (var request in _requests) { - request.completer.completeError(new StateError('The socket has closed')); + if (_requests.isNotEmpty) { + _onErrorController.add('Socket was closed with pending requests'); } _requests.clear(); @@ -117,21 +104,7 @@ class TWebSocket implements TSocket { } void _onMessage(MessageEvent event) { - Uint8List data; - - try { - data = - new Uint8List.fromList(CryptoUtils.base64StringToBytes(event.data)); - } on FormatException catch (_) { - _onErrorController - .add(new UnsupportedError('Expected a Base 64 encoded string.')); - } - - if (!_completers.isEmpty) { - _completers.removeAt(0).complete(data); - } - - _onMessageController.add(data); + _onMessageController.add(event.data); } void _onError(Event event) { @@ -139,10 +112,3 @@ class TWebSocket implements TSocket { _onErrorController.add(event.toString()); } } - -class _Request { - final Uint8List data; - final Completer completer; - - _Request(this.data, this.completer); -} diff --git a/lib/dart/lib/src/console/t_web_socket.dart b/lib/dart/lib/src/console/t_web_socket.dart index 8f54dfa2309..cc389fb0498 100644 --- a/lib/dart/lib/src/console/t_web_socket.dart +++ b/lib/dart/lib/src/console/t_web_socket.dart @@ -19,9 +19,7 @@ library thrift.src.console; import 'dart:async'; import 'dart:io'; -import 'dart:typed_data' show Uint8List; -import 'package:crypto/crypto.dart' show CryptoUtils; import 'package:thrift/thrift.dart'; /// A [TSocket] backed by a [WebSocket] from dart:io @@ -32,12 +30,10 @@ class TWebSocket implements TSocket { final StreamController _onErrorController; Stream get onError => _onErrorController.stream; - final StreamController _onMessageController; - Stream get onMessage => _onMessageController.stream; + final StreamController _onMessageController; + Stream get onMessage => _onMessageController.stream; - final List> _completers = []; - - TWebSocket(WebSocket socket, {this.isServer: true}) + TWebSocket(WebSocket socket) : _onStateController = new StreamController.broadcast(), _onErrorController = new StreamController.broadcast(), _onMessageController = new StreamController.broadcast() { @@ -49,8 +45,6 @@ class TWebSocket implements TSocket { _socket.listen(_onMessage, onError: _onError, onDone: close); } - final bool isServer; - WebSocket _socket; bool get isOpen => _socket != null; @@ -67,45 +61,15 @@ class TWebSocket implements TSocket { _socket = null; } - for (var completer in _completers) { - completer.completeError(new StateError('The socket has closed')); - } - _completers.clear(); - _onStateController.add(TSocketState.CLOSED); } - Future send(Uint8List data) async { - Future result; - if (isServer) { - result = new Future.value(); - } else { - // if we are a client, then we expect a result - Completer completer = new Completer(); - _completers.add(completer); - result = completer.future; - } - - _socket.add(CryptoUtils.bytesToBase64(data)); - - return result; + void send(String data) { + _socket.add(data); } - void _onMessage(Object message) { - Uint8List data; - - try { - data = new Uint8List.fromList(CryptoUtils.base64StringToBytes(message)); - } on FormatException catch (_) { - _onErrorController - .add(new UnsupportedError('Expected a Base 64 encoded string.')); - } - - if (!_completers.isEmpty) { - _completers.removeAt(0).complete(data); - } - - _onMessageController.add(data); + void _onMessage(String message) { + _onMessageController.add(message); } void _onError(Object error) { diff --git a/lib/dart/lib/src/transport/t_socket.dart b/lib/dart/lib/src/transport/t_socket.dart index c299d350410..d50acaf6cae 100644 --- a/lib/dart/lib/src/transport/t_socket.dart +++ b/lib/dart/lib/src/transport/t_socket.dart @@ -24,7 +24,7 @@ abstract class TSocket { Stream get onError; - Stream get onMessage; + Stream get onMessage; bool get isOpen; @@ -34,5 +34,5 @@ abstract class TSocket { Future close(); - Future send(Uint8List data); + void send(String data); } diff --git a/lib/dart/lib/src/transport/t_socket_transport.dart b/lib/dart/lib/src/transport/t_socket_transport.dart index 320a7d15974..5ee6d8051e2 100644 --- a/lib/dart/lib/src/transport/t_socket_transport.dart +++ b/lib/dart/lib/src/transport/t_socket_transport.dart @@ -21,32 +21,25 @@ part of thrift; /// /// For example: /// -/// var transport = new TSocketTransport(new TWebSocket(url)); +/// var transport = new TClientSocketTransport(new TWebSocket(url)); /// var protocol = new TBinaryProtocol(transport); /// var client = new MyThriftServiceClient(protocol); /// var result = client.myMethod(); /// /// Adapted from the JS WebSocket transport. -class TSocketTransport extends TBufferedTransport { - final TSocket socket; +abstract class TSocketTransport extends TBufferedTransport { final Logger log = new Logger('thrift.TSocketTransport'); - final bool isListening; + final TSocket socket; - /// A transport using the provided [socket]. If [isListening] is true, then - /// messages received from [socket] will be written to the transport and made - /// available for reading. - TSocketTransport(this.socket, {isListening: false}) - : this.isListening = isListening { + /// A transport using the provided [socket]. + TSocketTransport(this.socket) { if (socket == null) { throw new ArgumentError.notNull('socket'); } socket.onError.listen((String e) => log.warning(e)); - - if (isListening) { - socket.onMessage.listen(_onMessage); - } + socket.onMessage.listen(handleIncomingMessage); } bool get isOpen => socket.isOpen; @@ -61,17 +54,79 @@ class TSocketTransport extends TBufferedTransport { return socket.close(); } - Future flush() async { - if (isListening) { - _setReadBuffer(_consumeWriteBuffer()); - } else { - Uint8List result = await socket.send(_consumeWriteBuffer()); - _setReadBuffer(result); + /// Make an incoming message available to read from the transport. + void handleIncomingMessage(String message) { + Uint8List data; + + try { + data = + new Uint8List.fromList(CryptoUtils.base64StringToBytes(message)); + } on FormatException catch (_) { + throw new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Expected a Base 64 encoded string."); + } + + _setReadBuffer(data); + } + + /// Send the bytes in the write buffer to the socket + void sendMessage() { + Uint8List message = _consumeWriteBuffer(); + socket.send(CryptoUtils.bytesToBase64(message)); + } + +} + +/// [TClientSocketTransport] sends outgoing messages and expects a response +/// +/// NOTE: Currently this assumes serialized responses from a single threaded +/// server. +/// +/// TODO Give [TClientSocketTransport] more information so it can correlate +/// requests and responses, e.g. a protocol-aware function that can read the +/// sequence id from the message header. +class TClientSocketTransport extends TSocketTransport { + + final List> _completers = []; + + TClientSocketTransport(TSocket socket) : super(socket); + + Future flush() { + Completer completer = new Completer(); + _completers.add(completer); + + sendMessage(); + + return completer.future; + } + + void handleIncomingMessage(String message) { + super.handleIncomingMessage(message); + + if (_completers.isNotEmpty) { + _completers.removeAt(0).complete(); } } +} + +/// [TServerSocketTransport] listens for incoming messages. When it sends a +/// response, it does not expect an acknowledgement. +class TServerSocketTransport extends TSocketTransport { + + final StreamController _onIncomingMessageController; + Stream get onIncomingMessage => _onIncomingMessageController.stream; + + TServerSocketTransport(TSocket socket) + : _onIncomingMessageController = new StreamController.broadcast(), + super(socket); + + Future flush() async { + sendMessage(); + } + + void handleIncomingMessage(String message) { + super.handleIncomingMessage(message); - void _onMessage(Uint8List message) { - writeAll(message); - _setReadBuffer(_consumeWriteBuffer()); + _onIncomingMessageController.add(null); } } diff --git a/lib/dart/test/transport/t_socket_transport_test.dart b/lib/dart/test/transport/t_socket_transport_test.dart index fe1a6da3c80..4f0c1a45aa1 100644 --- a/lib/dart/test/transport/t_socket_transport_test.dart +++ b/lib/dart/test/transport/t_socket_transport_test.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:convert' show Utf8Codec; import 'dart:typed_data' show Uint8List; +import 'package:crypto/crypto.dart' show CryptoUtils; import 'package:test/test.dart'; import 'package:thrift/thrift.dart'; @@ -11,62 +12,85 @@ void main() { const utf8Codec = const Utf8Codec(); - final expectedText = 'my test data'; - final expectedBytes = new Uint8List.fromList(utf8Codec.encode(expectedText)); + final requestText = 'my test request'; + final requestBytes = new Uint8List.fromList(utf8Codec.encode(requestText)); + final requestBase64 = CryptoUtils.bytesToBase64(requestBytes); - test('Test transport listens to socket', () async { - var socket = new FakeSocket(isServer: true); - await socket.open(); - expect(socket.isOpen, isTrue); + final responseText = 'a response!'; + final responseBytes = new Uint8List.fromList(utf8Codec.encode(responseText)); + final responseBase64 = CryptoUtils.bytesToBase64(responseBytes); - var transport = new TSocketTransport(socket, isListening: true); - expect(transport.hasReadData, isFalse); + group('TClientTransport', () { + test('Test client sending data over transport', () async { + var socket = new FakeSocket(); + await socket.open(); - socket.receiveFakeMessage(expectedBytes); + var transport = new TClientSocketTransport(socket); - // allow microtask events to finish - await new Future.value(); + transport.writeAll(requestBytes); + expect(socket.sendPayload, isNull); - expect(transport.hasReadData, isTrue); + Future responseReady = transport.flush(); - var buffer = new Uint8List(expectedBytes.length); - transport.readAll(buffer, 0, expectedBytes.length); + // allow microtask events to finish + await new Future.value(); - var bufferText = utf8Codec.decode(buffer); - expect(bufferText, expectedText); - }); + expect(socket.sendPayload, isNotNull); + expect(socket.sendPayload, requestBase64); - test('Test transport does not listen to socket', () async { - var socket = new FakeSocket(); - await socket.open(); + // simulate a response + socket.receiveFakeMessage(responseBase64); - var transport = new TSocketTransport(socket, isListening: false); + await responseReady; + var buffer = new Uint8List(responseBytes.length); + transport.readAll(buffer, 0, responseBytes.length); + var bufferText = utf8Codec.decode(buffer); - socket.receiveFakeMessage(expectedBytes); + expect(bufferText, responseText); + }); + }, timeout: new Timeout(new Duration(seconds: 1))); - // allow microtask events to finish - await new Future.value(); + group('TServerTransport', () { + test('Test server transport listens to socket', () async { + var socket = new FakeSocket(); + await socket.open(); + expect(socket.isOpen, isTrue); - expect(transport.hasReadData, isFalse); - }); + var transport = new TServerSocketTransport(socket); + expect(transport.hasReadData, isFalse); - test('Test sending data over transport', () async { - var socket = new FakeSocket(isServer: true); - await socket.open(); + socket.receiveFakeMessage(requestBase64); - var transport = new TSocketTransport(socket, isListening: false); + // allow microtask events to finish + await new Future.value(); - transport.writeAll(expectedBytes); - expect(socket.sendPayload, isNull); + expect(transport.hasReadData, isTrue); - transport.flush(); + var buffer = new Uint8List(requestBytes.length); + transport.readAll(buffer, 0, requestBytes.length); - // allow microtask events to finish - await new Future.value(); + var bufferText = utf8Codec.decode(buffer); + expect(bufferText, requestText); + }); - expect(socket.sendPayload, isNotNull); - expect(utf8Codec.decode(socket.sendPayload), expectedText); - }); + test('Test server sending data over transport', () async { + var socket = new FakeSocket(); + await socket.open(); + + var transport = new TServerSocketTransport(socket); + + transport.writeAll(responseBytes); + expect(socket.sendPayload, isNull); + + transport.flush(); + + // allow microtask events to finish + await new Future.value(); + + expect(socket.sendPayload, isNotNull); + expect(socket.sendPayload, responseBase64); + }); + }, timeout: new Timeout(new Duration(seconds: 1))); } @@ -79,18 +103,14 @@ class FakeSocket extends TSocket { final StreamController _onErrorController; Stream get onError => _onErrorController.stream; - final StreamController _onMessageController; - Stream get onMessage => _onMessageController.stream; - - final List> _completers = []; + final StreamController _onMessageController; + Stream get onMessage => _onMessageController.stream; - FakeSocket({this.isServer: false}) + FakeSocket() : _onStateController = new StreamController.broadcast(), _onErrorController = new StreamController.broadcast(), _onMessageController = new StreamController.broadcast(); - final bool isServer; - bool _isOpen; bool get isOpen => _isOpen; @@ -104,40 +124,21 @@ class FakeSocket extends TSocket { Future close() async { _isOpen = false; - for (var completer in _completers) { - completer.completeError(new StateError('The socked has closed')); - } _onStateController.add(TSocketState.CLOSED); } - Uint8List _sendPayload; - - Uint8List get sendPayload => _sendPayload; + String _sendPayload; + String get sendPayload => _sendPayload; - Future send(Uint8List data) { + void send(String data) { if (!isOpen) throw new StateError("The socket is not open"); - Future result; - if (isServer) { - result = new Future.value(); - } else { - Completer completer = new Completer(); - _completers.add(completer); - result = completer.future; - } - _sendPayload = data; - - return result; } - void receiveFakeMessage(Uint8List message) { + void receiveFakeMessage(String message) { if (!isOpen) throw new StateError("The socket is not open"); - if (!_completers.isEmpty) { - _completers.removeAt(0).complete(message); - } - _onMessageController.add(message); } diff --git a/tutorial/dart/client/web/client.dart b/tutorial/dart/client/web/client.dart index 8c225cf267a..5b5fa9c376f 100644 --- a/tutorial/dart/client/web/client.dart +++ b/tutorial/dart/client/web/client.dart @@ -47,7 +47,7 @@ class CalculatorUI { } void _initConnection() { - _transport = new TSocketTransport( + _transport = new TClientSocketTransport( new TWebSocket(Uri.parse('ws://127.0.0.1:9090/ws'))); TProtocol protocol = new TJsonProtocol(_transport); _transport.open(); diff --git a/tutorial/dart/server/bin/main.dart b/tutorial/dart/server/bin/main.dart index 0fe7da2bce6..736b6c6f71b 100644 --- a/tutorial/dart/server/bin/main.dart +++ b/tutorial/dart/server/bin/main.dart @@ -1,18 +1,22 @@ import 'dart:async'; import 'dart:io'; -import 'dart:typed_data' show Uint8List; +import 'package:logging/logging.dart'; import 'package:thrift/thrift.dart'; import 'package:thrift/thrift_console.dart'; import 'package:tutorial/tutorial.dart'; import 'package:shared/shared.dart'; -TProtocol _inputProtocol; -TProtocol _outputProtocol; +TProtocol _protocol; TProcessor _processor; WebSocket _webSocket; main(List args) { + Logger.root.level = Level.ALL; + Logger.root.onRecord.listen((LogRecord rec) { + print('${rec.level.name}: ${rec.time}: ${rec.message}'); + }); + int port = 9090; if (!args.isEmpty) { port = int.parse(args[0]); @@ -40,21 +44,16 @@ Future _initWebSocket(HttpRequest request) async { _webSocket = await WebSocketTransformer.upgrade(request); TWebSocket socket = new TWebSocket(_webSocket); + TServerSocketTransport transport = new TServerSocketTransport(socket); + transport.onIncomingMessage.listen(_processMessage); _processor = new CalculatorProcessor(new CalculatorServer()); - _inputProtocol = - new TJsonProtocol(new TSocketTransport(socket, isListening: true)); - await _inputProtocol.transport.open(); - - _outputProtocol = new TJsonProtocol(new TSocketTransport(socket)); - await _outputProtocol.transport.open(); - - socket.onError.listen((error) => print('Error: $error')); - socket.onMessage.listen(_onMessage); + _protocol = new TJsonProtocol(transport); + await _protocol.transport.open(); } -Future _onMessage(Uint8List data) async { - _processor.process(_inputProtocol, _outputProtocol); +Future _processMessage(_) async { + _processor.process(_protocol, _protocol); } class CalculatorServer implements Calculator { From 4a5197e97fa504e33c2e020a1be5c8f10e8318ca Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Mon, 14 Sep 2015 11:17:21 -0500 Subject: [PATCH 26/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Move Base 64 encoding out of transport and back into web_socket implementations. --- lib/dart/lib/src/browser/t_web_socket.dart | 29 +++++++++++++------ lib/dart/lib/src/console/t_web_socket.dart | 20 +++++++++---- lib/dart/lib/src/transport/t_socket.dart | 4 +-- .../lib/src/transport/t_socket_transport.dart | 27 +++++------------ .../transport/t_socket_transport_test.dart | 18 +++++++----- 5 files changed, 54 insertions(+), 44 deletions(-) diff --git a/lib/dart/lib/src/browser/t_web_socket.dart b/lib/dart/lib/src/browser/t_web_socket.dart index a868c69caf8..ae90e3f30d7 100644 --- a/lib/dart/lib/src/browser/t_web_socket.dart +++ b/lib/dart/lib/src/browser/t_web_socket.dart @@ -22,7 +22,9 @@ import 'dart:html' show CloseEvent; import 'dart:html' show Event; import 'dart:html' show MessageEvent; import 'dart:html' show WebSocket; +import 'dart:typed_data' show Uint8List; +import 'package:crypto/crypto.dart' show CryptoUtils; import 'package:thrift/thrift.dart'; /// A [TSocket] backed by a [WebSocket] from dart:html @@ -35,10 +37,10 @@ class TWebSocket implements TSocket { final StreamController _onErrorController; Stream get onError => _onErrorController.stream; - final StreamController _onMessageController; - Stream get onMessage => _onMessageController.stream; + final StreamController _onMessageController; + Stream get onMessage => _onMessageController.stream; - final List _requests = []; + final List _requests = []; TWebSocket(this.url) : _onStateController = new StreamController.broadcast(), @@ -75,15 +77,15 @@ class TWebSocket implements TSocket { } } - void send(String data) { + void send(Uint8List data) { _requests.add(data); _sendRequests(); } void _sendRequests() { while (isOpen && _requests.isNotEmpty) { - String data = _requests.removeAt(0); - _socket.sendString(data); + Uint8List data = _requests.removeAt(0); + _socket.sendString(CryptoUtils.bytesToBase64(data)); } } @@ -96,15 +98,24 @@ class TWebSocket implements TSocket { _socket = null; if (_requests.isNotEmpty) { - _onErrorController.add('Socket was closed with pending requests'); + _onErrorController + .add(new StateError('Socket was closed with pending requests')); } _requests.clear(); _onStateController.add(TSocketState.CLOSED); } - void _onMessage(MessageEvent event) { - _onMessageController.add(event.data); + void _onMessage(MessageEvent message) { + try { + Uint8List data = + new Uint8List.fromList(CryptoUtils.base64StringToBytes(message.data)); + _onMessageController.add(data); + } on FormatException catch (_) { + var error = new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Expected a Base 64 encoded string."); + _onErrorController.add(error); + } } void _onError(Event event) { diff --git a/lib/dart/lib/src/console/t_web_socket.dart b/lib/dart/lib/src/console/t_web_socket.dart index cc389fb0498..fb9835524a1 100644 --- a/lib/dart/lib/src/console/t_web_socket.dart +++ b/lib/dart/lib/src/console/t_web_socket.dart @@ -19,7 +19,9 @@ library thrift.src.console; import 'dart:async'; import 'dart:io'; +import 'dart:typed_data' show Uint8List; +import 'package:crypto/crypto.dart' show CryptoUtils; import 'package:thrift/thrift.dart'; /// A [TSocket] backed by a [WebSocket] from dart:io @@ -30,8 +32,8 @@ class TWebSocket implements TSocket { final StreamController _onErrorController; Stream get onError => _onErrorController.stream; - final StreamController _onMessageController; - Stream get onMessage => _onMessageController.stream; + final StreamController _onMessageController; + Stream get onMessage => _onMessageController.stream; TWebSocket(WebSocket socket) : _onStateController = new StreamController.broadcast(), @@ -64,12 +66,20 @@ class TWebSocket implements TSocket { _onStateController.add(TSocketState.CLOSED); } - void send(String data) { - _socket.add(data); + void send(Uint8List data) { + _socket.add(CryptoUtils.bytesToBase64(data)); } void _onMessage(String message) { - _onMessageController.add(message); + try { + Uint8List data = + new Uint8List.fromList(CryptoUtils.base64StringToBytes(message)); + _onMessageController.add(data); + } on FormatException catch (_) { + var error = new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Expected a Base 64 encoded string."); + _onErrorController.add(error); + } } void _onError(Object error) { diff --git a/lib/dart/lib/src/transport/t_socket.dart b/lib/dart/lib/src/transport/t_socket.dart index d50acaf6cae..74618b63853 100644 --- a/lib/dart/lib/src/transport/t_socket.dart +++ b/lib/dart/lib/src/transport/t_socket.dart @@ -24,7 +24,7 @@ abstract class TSocket { Stream get onError; - Stream get onMessage; + Stream get onMessage; bool get isOpen; @@ -34,5 +34,5 @@ abstract class TSocket { Future close(); - void send(String data); + void send(Uint8List data); } diff --git a/lib/dart/lib/src/transport/t_socket_transport.dart b/lib/dart/lib/src/transport/t_socket_transport.dart index 5ee6d8051e2..476464ad222 100644 --- a/lib/dart/lib/src/transport/t_socket_transport.dart +++ b/lib/dart/lib/src/transport/t_socket_transport.dart @@ -55,26 +55,15 @@ abstract class TSocketTransport extends TBufferedTransport { } /// Make an incoming message available to read from the transport. - void handleIncomingMessage(String message) { - Uint8List data; - - try { - data = - new Uint8List.fromList(CryptoUtils.base64StringToBytes(message)); - } on FormatException catch (_) { - throw new TProtocolError(TProtocolErrorType.INVALID_DATA, - "Expected a Base 64 encoded string."); - } - - _setReadBuffer(data); + void handleIncomingMessage(Uint8List message) { + _setReadBuffer(message); } /// Send the bytes in the write buffer to the socket void sendMessage() { Uint8List message = _consumeWriteBuffer(); - socket.send(CryptoUtils.bytesToBase64(message)); + socket.send(message); } - } /// [TClientSocketTransport] sends outgoing messages and expects a response @@ -86,7 +75,6 @@ abstract class TSocketTransport extends TBufferedTransport { /// requests and responses, e.g. a protocol-aware function that can read the /// sequence id from the message header. class TClientSocketTransport extends TSocketTransport { - final List> _completers = []; TClientSocketTransport(TSocket socket) : super(socket); @@ -100,7 +88,7 @@ class TClientSocketTransport extends TSocketTransport { return completer.future; } - void handleIncomingMessage(String message) { + void handleIncomingMessage(Uint8List message) { super.handleIncomingMessage(message); if (_completers.isNotEmpty) { @@ -112,19 +100,18 @@ class TClientSocketTransport extends TSocketTransport { /// [TServerSocketTransport] listens for incoming messages. When it sends a /// response, it does not expect an acknowledgement. class TServerSocketTransport extends TSocketTransport { - final StreamController _onIncomingMessageController; Stream get onIncomingMessage => _onIncomingMessageController.stream; TServerSocketTransport(TSocket socket) - : _onIncomingMessageController = new StreamController.broadcast(), - super(socket); + : _onIncomingMessageController = new StreamController.broadcast(), + super(socket); Future flush() async { sendMessage(); } - void handleIncomingMessage(String message) { + void handleIncomingMessage(Uint8List message) { super.handleIncomingMessage(message); _onIncomingMessageController.add(null); diff --git a/lib/dart/test/transport/t_socket_transport_test.dart b/lib/dart/test/transport/t_socket_transport_test.dart index 4f0c1a45aa1..d08248c6891 100644 --- a/lib/dart/test/transport/t_socket_transport_test.dart +++ b/lib/dart/test/transport/t_socket_transport_test.dart @@ -36,7 +36,7 @@ void main() { await new Future.value(); expect(socket.sendPayload, isNotNull); - expect(socket.sendPayload, requestBase64); + expect(socket.sendPayload, requestBytes); // simulate a response socket.receiveFakeMessage(responseBase64); @@ -88,7 +88,7 @@ void main() { await new Future.value(); expect(socket.sendPayload, isNotNull); - expect(socket.sendPayload, responseBase64); + expect(socket.sendPayload, responseBytes); }); }, timeout: new Timeout(new Duration(seconds: 1))); @@ -103,8 +103,8 @@ class FakeSocket extends TSocket { final StreamController _onErrorController; Stream get onError => _onErrorController.stream; - final StreamController _onMessageController; - Stream get onMessage => _onMessageController.stream; + final StreamController _onMessageController; + Stream get onMessage => _onMessageController.stream; FakeSocket() : _onStateController = new StreamController.broadcast(), @@ -127,18 +127,20 @@ class FakeSocket extends TSocket { _onStateController.add(TSocketState.CLOSED); } - String _sendPayload; - String get sendPayload => _sendPayload; + Uint8List _sendPayload; + Uint8List get sendPayload => _sendPayload; - void send(String data) { + void send(Uint8List data) { if (!isOpen) throw new StateError("The socket is not open"); _sendPayload = data; } - void receiveFakeMessage(String message) { + void receiveFakeMessage(String base64) { if (!isOpen) throw new StateError("The socket is not open"); + var message = + new Uint8List.fromList(CryptoUtils.base64StringToBytes(base64)); _onMessageController.add(message); } From cd83635f4ae688d2ae65d474790a213294576d15 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Mon, 14 Sep 2015 11:35:52 -0500 Subject: [PATCH 27/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Return Futures from open and close that actually reflect the state of the WebSocket. --- lib/dart/lib/src/browser/t_web_socket.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/dart/lib/src/browser/t_web_socket.dart b/lib/dart/lib/src/browser/t_web_socket.dart index ae90e3f30d7..dfcee8391fe 100644 --- a/lib/dart/lib/src/browser/t_web_socket.dart +++ b/lib/dart/lib/src/browser/t_web_socket.dart @@ -58,7 +58,7 @@ class TWebSocket implements TSocket { bool get isClosed => _socket == null || _socket.readyState == WebSocket.CLOSED; - Future open() async { + Future open() { if (!isClosed) { throw new TTransportError( TTransportErrorType.ALREADY_OPEN, 'Socket already connected'); @@ -69,11 +69,16 @@ class TWebSocket implements TSocket { _socket.onOpen.listen(_onOpen); _socket.onClose.listen(_onClose); _socket.onMessage.listen(_onMessage); + + return _socket.onOpen.first; } - Future close() async { + Future close() { if (_socket != null) { _socket.close(); + return _socket.onClose.first; + } else { + return new Future.value(); } } From 544834b073f253935b4ee96f8f805632083fb1c1 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Mon, 14 Sep 2015 15:48:01 -0500 Subject: [PATCH 28/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Avoid using String.codeUnits as a Uint8List. Cleanup bad type assignments in tutorial client. --- .../lib/src/protocol/t_json_protocol.dart | 166 +++++++++--------- lib/dart/lib/src/transport/t_transport.dart | 8 +- tutorial/dart/client/web/client.dart | 4 +- 3 files changed, 89 insertions(+), 89 deletions(-) diff --git a/lib/dart/lib/src/protocol/t_json_protocol.dart b/lib/dart/lib/src/protocol/t_json_protocol.dart index 73cf8ee2d8a..b42b3b669f0 100644 --- a/lib/dart/lib/src/protocol/t_json_protocol.dart +++ b/lib/dart/lib/src/protocol/t_json_protocol.dart @@ -50,23 +50,22 @@ class TJsonProtocol extends TProtocol { } /// Read a byte that must match [char]; otherwise throw a [TProtocolError]. - void _readJsonSyntaxChar(String char) { - int charByte = char.codeUnitAt(0); + void _readJsonSyntaxChar(int charByte) { int byte = _reader.read(); if (byte != charByte) { throw new TProtocolError(TProtocolErrorType.INVALID_DATA, - "Expected character $char but found: ${new String.fromCharCode(byte)}"); + "Expected character ${new String.fromCharCode(charByte)} but found: ${new String.fromCharCode(byte)}"); } } int _hexVal(int byte) { - if (byte >= _Constants.HEX_0.codeUnitAt(0) && - byte <= _Constants.HEX_9.codeUnitAt(0)) { - return byte - _Constants.HEX_0.codeUnitAt(0); - } else if (byte >= _Constants.HEX_A.codeUnitAt(0) && - byte <= _Constants.HEX_F.codeUnitAt(0)) { + if (byte >= _Constants.HEX_0_BYTES[0] && + byte <= _Constants.HEX_9_BYTES[0]) { + return byte - _Constants.HEX_0_BYTES[0]; + } else if (byte >= _Constants.HEX_A_BYTES[0] && + byte <= _Constants.HEX_F_BYTES[0]) { byte += 10; - return byte - _Constants.HEX_A.codeUnitAt(0); + return byte - _Constants.HEX_A_BYTES[0]; } else { throw new TProtocolError( TProtocolErrorType.INVALID_DATA, "Expected hex character"); @@ -80,15 +79,15 @@ class TJsonProtocol extends TProtocol { /// Write the [bytes] as JSON characters, escaping as needed. void _writeJsonString(Uint8List bytes) { _context.write(); - transport.writeAll(_Constants.QUOTE.codeUnits); + transport.writeAll(_Constants.QUOTE_BYTES); int length = bytes.length; for (int i = 0; i < length; i++) { int byte = bytes[i]; if ((byte & 0x00FF) >= 0x30) { - if (byte == _Constants.BACKSLASH.codeUnitAt(0)) { - transport.writeAll(_Constants.BACKSLASH.codeUnits); - transport.writeAll(_Constants.BACKSLASH.codeUnits); + if (byte == _Constants.BACKSLASH_BYTES[0]) { + transport.writeAll(_Constants.BACKSLASH_BYTES); + transport.writeAll(_Constants.BACKSLASH_BYTES); } else { transport.write(bytes, i, 1); } @@ -97,10 +96,10 @@ class TJsonProtocol extends TProtocol { if (_tempBuffer[0] == 1) { transport.write(bytes, i, 1); } else if (_tempBuffer[0] > 1) { - transport.writeAll(_Constants.BACKSLASH.codeUnits); + transport.writeAll(_Constants.BACKSLASH_BYTES); transport.write(_tempBuffer, 0, 1); } else { - transport.writeAll(_Constants.ESCSEQ.codeUnits); + transport.writeAll(_Constants.ESCSEQ_BYTES); _tempBuffer[0] = _hexChar(byte >> 4); _tempBuffer[1] = _hexChar(byte); transport.write(_tempBuffer, 0, 2); @@ -108,7 +107,7 @@ class TJsonProtocol extends TProtocol { } } - transport.writeAll(_Constants.QUOTE.codeUnits); + transport.writeAll(_Constants.QUOTE_BYTES); } void _writeJsonInteger(int i) { @@ -116,11 +115,11 @@ class TJsonProtocol extends TProtocol { String str = i.toString(); if (_context.escapeNumbers) { - transport.writeAll(_Constants.QUOTE.codeUnits); + transport.writeAll(_Constants.QUOTE_BYTES); } transport.writeAll(utf8Codec.encode(str)); if (_context.escapeNumbers) { - transport.writeAll(_Constants.QUOTE.codeUnits); + transport.writeAll(_Constants.QUOTE_BYTES); } } @@ -130,44 +129,44 @@ class TJsonProtocol extends TProtocol { bool escapeNumbers = d.isNaN || d.isInfinite || _context.escapeNumbers; if (escapeNumbers) { - transport.writeAll(_Constants.QUOTE.codeUnits); + transport.writeAll(_Constants.QUOTE_BYTES); } transport.writeAll(utf8Codec.encode(str)); if (escapeNumbers) { - transport.writeAll(_Constants.QUOTE.codeUnits); + transport.writeAll(_Constants.QUOTE_BYTES); } } void _writeJsonBase64(Uint8List bytes) { _context.write(); - transport.writeAll(_Constants.QUOTE.codeUnits); + transport.writeAll(_Constants.QUOTE_BYTES); String base64 = CryptoUtils.bytesToBase64(bytes); transport.writeAll(utf8Codec.encode(base64)); - transport.writeAll(_Constants.QUOTE.codeUnits); + transport.writeAll(_Constants.QUOTE_BYTES); } void _writeJsonObjectStart() { _context.write(); - transport.writeAll(_Constants.LBRACE.codeUnits); + transport.writeAll(_Constants.LBRACE_BYTES); _pushContext(new _PairContext(this)); } void _writeJsonObjectEnd() { _popContext(); - transport.writeAll(_Constants.RBRACE.codeUnits); + transport.writeAll(_Constants.RBRACE_BYTES); } void _writeJsonArrayStart() { _context.write(); - transport.writeAll(_Constants.LBRACKET.codeUnits); + transport.writeAll(_Constants.LBRACKET_BYTES); _pushContext(new _ListContext(this)); } void _writeJsonArrayEnd() { _popContext(); - transport.writeAll(_Constants.RBRACKET.codeUnits); + transport.writeAll(_Constants.RBRACKET_BYTES); } void writeMessageBegin(TMessage message) { @@ -196,7 +195,7 @@ class TJsonProtocol extends TProtocol { void writeFieldBegin(TField field) { _writeJsonInteger(field.id); _writeJsonObjectStart(); - _writeJsonString(_Constants.getTypeNameForTypeId(field.type).codeUnits); + _writeJsonString(_Constants.getTypeNameBytesForTypeId(field.type)); } void writeFieldEnd() { @@ -207,8 +206,8 @@ class TJsonProtocol extends TProtocol { void writeMapBegin(TMap map) { _writeJsonArrayStart(); - _writeJsonString(_Constants.getTypeNameForTypeId(map.keyType).codeUnits); - _writeJsonString(_Constants.getTypeNameForTypeId(map.valueType).codeUnits); + _writeJsonString(_Constants.getTypeNameBytesForTypeId(map.keyType)); + _writeJsonString(_Constants.getTypeNameBytesForTypeId(map.valueType)); _writeJsonInteger(map.length); _writeJsonObjectStart(); } @@ -220,8 +219,7 @@ class TJsonProtocol extends TProtocol { void writeListBegin(TList list) { _writeJsonArrayStart(); - _writeJsonString( - _Constants.getTypeNameForTypeId(list.elementType).codeUnits); + _writeJsonString(_Constants.getTypeNameBytesForTypeId(list.elementType)); _writeJsonInteger(list.length); } @@ -231,8 +229,7 @@ class TJsonProtocol extends TProtocol { void writeSetBegin(TSet set) { _writeJsonArrayStart(); - _writeJsonString( - _Constants.getTypeNameForTypeId(set.elementType).codeUnits); + _writeJsonString(_Constants.getTypeNameBytesForTypeId(set.elementType)); _writeJsonInteger(set.length); } @@ -281,15 +278,15 @@ class TJsonProtocol extends TProtocol { _context.read(); } - _readJsonSyntaxChar(_Constants.QUOTE); + _readJsonSyntaxChar(_Constants.QUOTE_BYTES[0]); while (true) { int byte = _reader.read(); - if (byte == _Constants.QUOTE.codeUnitAt(0)) { + if (byte == _Constants.QUOTE_BYTES[0]) { break; } // escaped? - if (byte != _Constants.ESCSEQ.codeUnitAt(0)) { + if (byte != _Constants.ESCSEQ_BYTES[0]) { bytes.add(byte); continue; } @@ -297,7 +294,7 @@ class TJsonProtocol extends TProtocol { byte = _reader.read(); // distinguish between \u00XX and control chars like \n - if (byte != _Constants.ESCSEQ.codeUnitAt(1)) { + if (byte != _Constants.ESCSEQ_BYTES[1]) { String char = new String.fromCharCode(byte); int offset = _Constants.ESCAPE_CHARS.indexOf(char); if (offset == -1) { @@ -310,8 +307,8 @@ class TJsonProtocol extends TProtocol { } // it's \u00XX - _readJsonSyntaxChar(_Constants.HEX_0); - _readJsonSyntaxChar(_Constants.HEX_0); + _readJsonSyntaxChar(_Constants.HEX_0_BYTES[0]); + _readJsonSyntaxChar(_Constants.HEX_0_BYTES[0]); transport.readAll(_tempBuffer, 0, 2); byte = _hexVal(_tempBuffer[0]) << 4 + _hexVal(_tempBuffer[1]); bytes.add(byte); @@ -335,11 +332,11 @@ class TJsonProtocol extends TProtocol { _context.read(); if (_context.escapeNumbers) { - _readJsonSyntaxChar(_Constants.QUOTE); + _readJsonSyntaxChar(_Constants.QUOTE_BYTES[0]); } String str = _readJsonNumericChars(); if (_context.escapeNumbers) { - _readJsonSyntaxChar(_Constants.QUOTE); + _readJsonSyntaxChar(_Constants.QUOTE_BYTES[0]); } try { @@ -353,7 +350,7 @@ class TJsonProtocol extends TProtocol { double _readJsonDouble() { _context.read(); - if (_reader.peek() == _Constants.QUOTE.codeUnitAt(0)) { + if (_reader.peek() == _Constants.QUOTE_BYTES[0]) { Uint8List bytes = _readJsonString(skipContext: true); double d = double.parse(utf8Codec.decode(bytes), (_) { throw new TProtocolError(TProtocolErrorType.INVALID_DATA, @@ -367,7 +364,7 @@ class TJsonProtocol extends TProtocol { } else { if (_context.escapeNumbers) { // This will throw - we should have had a quote if escapeNumbers == true - _readJsonSyntaxChar(_Constants.QUOTE); + _readJsonSyntaxChar(_Constants.QUOTE_BYTES[0]); } return double.parse(_readJsonNumericChars(), (_) { throw new TProtocolError(TProtocolErrorType.INVALID_DATA, @@ -386,23 +383,23 @@ class TJsonProtocol extends TProtocol { void _readJsonObjectStart() { _context.read(); - _readJsonSyntaxChar(_Constants.LBRACE); + _readJsonSyntaxChar(_Constants.LBRACE_BYTES[0]); _pushContext(new _PairContext(this)); } void _readJsonObjectEnd() { - _readJsonSyntaxChar(_Constants.RBRACE); + _readJsonSyntaxChar(_Constants.RBRACE_BYTES[0]); _popContext(); } void _readJsonArrayStart() { _context.read(); - _readJsonSyntaxChar(_Constants.LBRACKET); + _readJsonSyntaxChar(_Constants.LBRACKET_BYTES[0]); _pushContext(new _ListContext(this)); } void _readJsonArrayEnd() { - _readJsonSyntaxChar(_Constants.RBRACKET); + _readJsonSyntaxChar(_Constants.RBRACKET_BYTES[0]); _popContext(); } @@ -441,7 +438,7 @@ class TJsonProtocol extends TProtocol { int type = TType.STOP; int id = 0; - if (_reader.peek() != _Constants.RBRACE.codeUnitAt(0)) { + if (_reader.peek() != _Constants.RBRACE_BYTES[0]) { id = _readJsonInteger(); _readJsonObjectStart(); type = _Constants.getTypeIdForTypeName(_readJsonString()); @@ -529,20 +526,21 @@ class TJsonProtocol extends TProtocol { class _Constants { static const utf8codec = const Utf8Codec(); - static const String HEX_0 = '0'; - static const String HEX_9 = '9'; - static const String HEX_A = 'a'; - static const String HEX_F = 'f'; - static const String COMMA = ','; - static const String COLON = ':'; - static const String LBRACE = '{'; - static const String RBRACE = '}'; - static const String LBRACKET = '['; - static const String RBRACKET = ']'; - static const String QUOTE = '"'; - static const String BACKSLASH = r'\'; - - static const String ESCSEQ = r'\u00'; + static final Uint8List HEX_0_BYTES = new Uint8List.fromList('0'.codeUnits); + static final Uint8List HEX_9_BYTES = new Uint8List.fromList('9'.codeUnits); + static final Uint8List HEX_A_BYTES = new Uint8List.fromList('a'.codeUnits); + static final Uint8List HEX_F_BYTES = new Uint8List.fromList('f'.codeUnits); + static final Uint8List COMMA_BYTES = new Uint8List.fromList(','.codeUnits); + static final Uint8List COLON_BYTES = new Uint8List.fromList(':'.codeUnits); + static final Uint8List LBRACE_BYTES = new Uint8List.fromList('{'.codeUnits); + static final Uint8List RBRACE_BYTES = new Uint8List.fromList('}'.codeUnits); + static final Uint8List LBRACKET_BYTES = new Uint8List.fromList('['.codeUnits); + static final Uint8List RBRACKET_BYTES = new Uint8List.fromList(']'.codeUnits); + static final Uint8List QUOTE_BYTES = new Uint8List.fromList('"'.codeUnits); + static final Uint8List BACKSLASH_BYTES = + new Uint8List.fromList(r'\'.codeUnits); + + static final ESCSEQ_BYTES = new Uint8List.fromList(r'\u00'.codeUnits); static final Uint8List JSON_CHAR_TABLE = new Uint8List.fromList([ 0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes @@ -569,27 +567,28 @@ class _Constants { static const String NAME_LIST = 'lst'; static const String NAME_SET = 'set'; - static final Map _TYPE_ID_TO_NAME = new Map.unmodifiable({ - TType.BOOL: NAME_BOOL, - TType.BYTE: NAME_BYTE, - TType.I16: NAME_I16, - TType.I32: NAME_I32, - TType.I64: NAME_I64, - TType.DOUBLE: NAME_DOUBLE, - TType.STRING: NAME_STRING, - TType.STRUCT: NAME_STRUCT, - TType.MAP: NAME_MAP, - TType.SET: NAME_SET, - TType.LIST: NAME_LIST + static final Map _TYPE_ID_TO_NAME_BYTES = + new Map.unmodifiable({ + TType.BOOL: new Uint8List.fromList(NAME_BOOL.codeUnits), + TType.BYTE: new Uint8List.fromList(NAME_BYTE.codeUnits), + TType.I16: new Uint8List.fromList(NAME_I16.codeUnits), + TType.I32: new Uint8List.fromList(NAME_I32.codeUnits), + TType.I64: new Uint8List.fromList(NAME_I64.codeUnits), + TType.DOUBLE: new Uint8List.fromList(NAME_DOUBLE.codeUnits), + TType.STRING: new Uint8List.fromList(NAME_STRING.codeUnits), + TType.STRUCT: new Uint8List.fromList(NAME_STRUCT.codeUnits), + TType.MAP: new Uint8List.fromList(NAME_MAP.codeUnits), + TType.SET: new Uint8List.fromList(NAME_SET.codeUnits), + TType.LIST: new Uint8List.fromList(NAME_LIST.codeUnits) }); - static String getTypeNameForTypeId(int typeId) { - if (!_TYPE_ID_TO_NAME.containsKey(typeId)) { + static Uint8List getTypeNameBytesForTypeId(int typeId) { + if (!_TYPE_ID_TO_NAME_BYTES.containsKey(typeId)) { throw new TProtocolError( TProtocolErrorType.NOT_IMPLEMENTED, "Unrecognized type"); } - return _TYPE_ID_TO_NAME[typeId]; + return _TYPE_ID_TO_NAME_BYTES[typeId]; } static final Map _NAME_TO_TYPE_ID = new Map.unmodifiable({ @@ -644,7 +643,7 @@ class _LookaheadReader { _LookaheadReader(this.protocol); - bool _hasData; + bool _hasData = false; final Uint8List _data = new Uint8List(1); int read() { @@ -688,7 +687,7 @@ class _ListContext extends _BaseContext { if (_first) { _first = false; } else { - protocol.transport.writeAll(_Constants.COMMA.codeUnits); + protocol.transport.writeAll(_Constants.COMMA_BYTES); } } @@ -696,7 +695,7 @@ class _ListContext extends _BaseContext { if (_first) { _first = false; } else { - protocol._readJsonSyntaxChar(_Constants.COMMA); + protocol._readJsonSyntaxChar(_Constants.COMMA_BYTES[0]); } } } @@ -707,14 +706,15 @@ class _PairContext extends _BaseContext { bool _first = true; bool _colon = true; - String get symbol => _colon ? _Constants.COLON : _Constants.COMMA; + Uint8List get symbolBytes => + _colon ? _Constants.COLON_BYTES : _Constants.COMMA_BYTES; void write() { if (_first) { _first = false; _colon = true; } else { - protocol.transport.writeAll(symbol.codeUnits); + protocol.transport.writeAll(symbolBytes); _colon = !_colon; } } @@ -724,7 +724,7 @@ class _PairContext extends _BaseContext { _first = false; _colon = true; } else { - protocol._readJsonSyntaxChar(symbol); + protocol._readJsonSyntaxChar(symbolBytes[0]); _colon = !_colon; } } diff --git a/lib/dart/lib/src/transport/t_transport.dart b/lib/dart/lib/src/transport/t_transport.dart index 404c4b73276..563d5eb5a18 100644 --- a/lib/dart/lib/src/transport/t_transport.dart +++ b/lib/dart/lib/src/transport/t_transport.dart @@ -54,16 +54,16 @@ abstract class TTransport { return got; } + /// Writes up to [len] bytes from the buffer. + /// Throws [TTransportError] if there was an error writing data + void write(Uint8List buffer, int offset, int length); + /// Writes the [bytes] to the output. /// Throws [TTransportError] if there was an error writing data void writeAll(Uint8List buffer) { write(buffer, 0, buffer.length); } - /// Writes up to [len] bytes from the buffer. - /// Throws [TTransportError] if there was an error writing data - void write(Uint8List buffer, int offset, int length); - /// Flush any pending data out of a transport buffer. /// Throws [TTransportError] if there was an error writing out data. Future flush(); diff --git a/tutorial/dart/client/web/client.dart b/tutorial/dart/client/web/client.dart index 5b5fa9c376f..c7af6074706 100644 --- a/tutorial/dart/client/web/client.dart +++ b/tutorial/dart/client/web/client.dart @@ -214,7 +214,7 @@ class CalculatorUI { InputElement num2 = querySelector("#calc2"); SelectElement op = querySelector("#calcOp"); SpanElement result = querySelector("#calcResult"); - SelectElement logId = querySelector("#logId"); + InputElement logId = querySelector("#logId"); InputElement comment = querySelector("#comment"); int logIdValue = int.parse(logId.value); @@ -266,7 +266,7 @@ class CalculatorUI { _validate(); InputElement structKey = querySelector("#structKey"); - SpanElement result = querySelector("#getStructResult"); + TextAreaElement result = querySelector("#getStructResult"); _calculatorClient .getStruct(int.parse(structKey.value)) From 655722d861dc59acc773062d647f61e8c9170756 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Mon, 14 Sep 2015 18:41:33 -0500 Subject: [PATCH 29/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Add TMessageReader and use in TClientSocketTransport to correlate requests and responses by seqid --- .../lib/src/protocol/t_binary_protocol.dart | 12 ++++ .../lib/src/protocol/t_json_protocol.dart | 6 ++ .../lib/src/protocol/t_protocol_factory.dart | 22 ++++++ .../lib/src/transport/t_message_reader.dart | 70 +++++++++++++++++++ .../lib/src/transport/t_socket_transport.dart | 49 ++++++------- lib/dart/lib/thrift.dart | 2 + lib/dart/pubspec.yaml | 1 + .../transport/t_socket_transport_test.dart | 27 ++++++- tutorial/dart/client/web/client.dart | 2 +- 9 files changed, 163 insertions(+), 28 deletions(-) create mode 100644 lib/dart/lib/src/protocol/t_protocol_factory.dart create mode 100644 lib/dart/lib/src/transport/t_message_reader.dart diff --git a/lib/dart/lib/src/protocol/t_binary_protocol.dart b/lib/dart/lib/src/protocol/t_binary_protocol.dart index ac8e0a42859..acaf3bb831b 100644 --- a/lib/dart/lib/src/protocol/t_binary_protocol.dart +++ b/lib/dart/lib/src/protocol/t_binary_protocol.dart @@ -17,6 +17,18 @@ part of thrift; +class TBinaryProtocolFactory implements TProtocolFactory { + TBinaryProtocolFactory({this.strictRead: false, this.strictWrite: true}); + + final bool strictRead; + final bool strictWrite; + + TBinaryProtocol getProtocol(TTransport transport) { + return new TBinaryProtocol(transport, + strictRead: strictRead, strictWrite: strictWrite); + } +} + /// Binary protocol implementation for Thrift. /// /// Adapted from the C# version. diff --git a/lib/dart/lib/src/protocol/t_json_protocol.dart b/lib/dart/lib/src/protocol/t_json_protocol.dart index b42b3b669f0..e644a636822 100644 --- a/lib/dart/lib/src/protocol/t_json_protocol.dart +++ b/lib/dart/lib/src/protocol/t_json_protocol.dart @@ -17,6 +17,12 @@ part of thrift; +class TJsonProtocolFactory implements TProtocolFactory { + TJsonProtocol getProtocol(TTransport transport) { + return new TJsonProtocol(transport); + } +} + /// JSON protocol implementation for Thrift. /// /// Adapted from the C# version. diff --git a/lib/dart/lib/src/protocol/t_protocol_factory.dart b/lib/dart/lib/src/protocol/t_protocol_factory.dart new file mode 100644 index 00000000000..922c6cb691f --- /dev/null +++ b/lib/dart/lib/src/protocol/t_protocol_factory.dart @@ -0,0 +1,22 @@ +/// 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. + +part of thrift; + +abstract class TProtocolFactory { + T getProtocol(TTransport transport); +} diff --git a/lib/dart/lib/src/transport/t_message_reader.dart b/lib/dart/lib/src/transport/t_message_reader.dart new file mode 100644 index 00000000000..f4428e7a8e1 --- /dev/null +++ b/lib/dart/lib/src/transport/t_message_reader.dart @@ -0,0 +1,70 @@ +/// 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. + +part of thrift; + +/// [TMessageReader] extracts a [TMessage] from bytes. This is used to allow a +/// transport to inspect the message seqid and map responses to requests. +class TMessageReader extends TTransport { + final TProtocolFactory protocolFactory; + + TMessageReader(this.protocolFactory); + + Iterator _readIterator; + + TMessage readMessage(Uint8List bytes) { + TProtocol protocol = protocolFactory.getProtocol(this); + _readIterator = bytes.iterator; + TMessage message = protocol.readMessageBegin(); + _readIterator = null; + + return message; + } + + get isOpen => true; + + Future open() => throw new UnsupportedError("Unsuppored in MessageReader"); + + Future close() => throw new UnsupportedError("Unsuppored in MessageReader"); + + int read(Uint8List buffer, int offset, int length) { + if (buffer == null) { + throw new ArgumentError.notNull("buffer"); + } + + if (offset + length > buffer.length) { + throw new ArgumentError("The range exceeds the buffer length"); + } + + if (_readIterator == null || length <= 0) { + return 0; + } + + int i = 0; + while (i < length && _readIterator.moveNext()) { + buffer[offset + i] = _readIterator.current; + i++; + } + + return i; + } + + void write(Uint8List buffer, int offset, int length) => + throw new UnsupportedError("Unsuppored in MessageReader"); + + Future flush() => throw new UnsupportedError("Unsuppored in MessageReader"); +} diff --git a/lib/dart/lib/src/transport/t_socket_transport.dart b/lib/dart/lib/src/transport/t_socket_transport.dart index 476464ad222..276cf6eca77 100644 --- a/lib/dart/lib/src/transport/t_socket_transport.dart +++ b/lib/dart/lib/src/transport/t_socket_transport.dart @@ -55,44 +55,40 @@ abstract class TSocketTransport extends TBufferedTransport { } /// Make an incoming message available to read from the transport. - void handleIncomingMessage(Uint8List message) { - _setReadBuffer(message); - } - - /// Send the bytes in the write buffer to the socket - void sendMessage() { - Uint8List message = _consumeWriteBuffer(); - socket.send(message); + void handleIncomingMessage(Uint8List messageBytes) { + _setReadBuffer(messageBytes); } } /// [TClientSocketTransport] sends outgoing messages and expects a response -/// -/// NOTE: Currently this assumes serialized responses from a single threaded -/// server. -/// -/// TODO Give [TClientSocketTransport] more information so it can correlate -/// requests and responses, e.g. a protocol-aware function that can read the -/// sequence id from the message header. class TClientSocketTransport extends TSocketTransport { - final List> _completers = []; + final Map> _completers = {}; - TClientSocketTransport(TSocket socket) : super(socket); + final TMessageReader messageReader; + + TClientSocketTransport(TSocket socket, TProtocolFactory protocolFactory) + : messageReader = new TMessageReader(protocolFactory), + super(socket); Future flush() { + Uint8List bytes = _consumeWriteBuffer(); + TMessage message = messageReader.readMessage(bytes); + Completer completer = new Completer(); - _completers.add(completer); + _completers[message.seqid] = completer; - sendMessage(); + socket.send(bytes); return completer.future; } - void handleIncomingMessage(Uint8List message) { - super.handleIncomingMessage(message); + void handleIncomingMessage(Uint8List messageBytes) { + super.handleIncomingMessage(messageBytes); - if (_completers.isNotEmpty) { - _completers.removeAt(0).complete(); + TMessage message = messageReader.readMessage(messageBytes); + var completer = _completers.remove(message.seqid); + if (completer != null) { + completer.complete(); } } } @@ -108,11 +104,12 @@ class TServerSocketTransport extends TSocketTransport { super(socket); Future flush() async { - sendMessage(); + Uint8List message = _consumeWriteBuffer(); + socket.send(message); } - void handleIncomingMessage(Uint8List message) { - super.handleIncomingMessage(message); + void handleIncomingMessage(Uint8List messageBytes) { + super.handleIncomingMessage(messageBytes); _onIncomingMessageController.add(null); } diff --git a/lib/dart/lib/thrift.dart b/lib/dart/lib/thrift.dart index f676a0fc7bc..4d67adf6b5c 100644 --- a/lib/dart/lib/thrift.dart +++ b/lib/dart/lib/thrift.dart @@ -41,6 +41,7 @@ part 'src/protocol/t_multiplexed_protocol.dart'; part 'src/protocol/t_protocol.dart'; part 'src/protocol/t_protocol_decorator.dart'; part 'src/protocol/t_protocol_error.dart'; +part 'src/protocol/t_protocol_factory.dart'; part 'src/protocol/t_protocol_util.dart'; part 'src/protocol/t_set.dart'; part 'src/protocol/t_struct.dart'; @@ -49,6 +50,7 @@ part 'src/protocol/t_type.dart'; part 'src/transport/t_buffered_transport.dart'; part 'src/transport/t_framed_transport.dart'; part 'src/transport/t_http_transport.dart'; +part 'src/transport/t_message_reader.dart'; part 'src/transport/t_socket.dart'; part 'src/transport/t_transport.dart'; part 'src/transport/t_transport_error.dart'; diff --git a/lib/dart/pubspec.yaml b/lib/dart/pubspec.yaml index b1f0919b58a..142fbb4bffa 100644 --- a/lib/dart/pubspec.yaml +++ b/lib/dart/pubspec.yaml @@ -12,4 +12,5 @@ dependencies: http: ^0.11.3 logging: ^0.11.0 dev_dependencies: + mockito: ^0.11.0 test: ^0.12.0 diff --git a/lib/dart/test/transport/t_socket_transport_test.dart b/lib/dart/test/transport/t_socket_transport_test.dart index d08248c6891..05560043f17 100644 --- a/lib/dart/test/transport/t_socket_transport_test.dart +++ b/lib/dart/test/transport/t_socket_transport_test.dart @@ -5,6 +5,7 @@ import 'dart:convert' show Utf8Codec; import 'dart:typed_data' show Uint8List; import 'package:crypto/crypto.dart' show CryptoUtils; +import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; import 'package:thrift/thrift.dart'; @@ -25,7 +26,9 @@ void main() { var socket = new FakeSocket(); await socket.open(); - var transport = new TClientSocketTransport(socket); + FakeProtocolFactory protocolFactory = new FakeProtocolFactory(); + protocolFactory.message = new TMessage("foo", TMessageType.CALL, 123); + var transport = new TClientSocketTransport(socket, protocolFactory); transport.writeAll(requestBytes); expect(socket.sendPayload, isNull); @@ -39,6 +42,7 @@ void main() { expect(socket.sendPayload, requestBytes); // simulate a response + protocolFactory.message = new TMessage("foo", TMessageType.REPLY, 123); socket.receiveFakeMessage(responseBase64); await responseReady; @@ -145,3 +149,24 @@ class FakeSocket extends TSocket { } } + + +class FakeProtocolFactory implements TProtocolFactory { + + FakeProtocolFactory(); + + TMessage message; + + getProtocol(TTransport transport) => new FakeProtocol(message); + +} + +class FakeProtocol extends Mock implements TProtocol { + + FakeProtocol(this._message); + + TMessage _message; + + readMessageBegin() => _message; + +} diff --git a/tutorial/dart/client/web/client.dart b/tutorial/dart/client/web/client.dart index c7af6074706..ae432c5b91e 100644 --- a/tutorial/dart/client/web/client.dart +++ b/tutorial/dart/client/web/client.dart @@ -48,7 +48,7 @@ class CalculatorUI { void _initConnection() { _transport = new TClientSocketTransport( - new TWebSocket(Uri.parse('ws://127.0.0.1:9090/ws'))); + new TWebSocket(Uri.parse('ws://127.0.0.1:9090/ws')), new TJsonProtocolFactory()); TProtocol protocol = new TJsonProtocol(_transport); _transport.open(); From 343a9bf701d02e2b6b9479acccbbff63d0e91e0b Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Tue, 15 Sep 2015 10:16:23 -0500 Subject: [PATCH 30/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Fix typo in error message. --- lib/dart/lib/src/transport/t_message_reader.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/dart/lib/src/transport/t_message_reader.dart b/lib/dart/lib/src/transport/t_message_reader.dart index f4428e7a8e1..cba101b9615 100644 --- a/lib/dart/lib/src/transport/t_message_reader.dart +++ b/lib/dart/lib/src/transport/t_message_reader.dart @@ -37,9 +37,9 @@ class TMessageReader extends TTransport { get isOpen => true; - Future open() => throw new UnsupportedError("Unsuppored in MessageReader"); + Future open() => throw new UnsupportedError("Unsupported in MessageReader"); - Future close() => throw new UnsupportedError("Unsuppored in MessageReader"); + Future close() => throw new UnsupportedError("Unsupported in MessageReader"); int read(Uint8List buffer, int offset, int length) { if (buffer == null) { @@ -64,7 +64,7 @@ class TMessageReader extends TTransport { } void write(Uint8List buffer, int offset, int length) => - throw new UnsupportedError("Unsuppored in MessageReader"); + throw new UnsupportedError("Unsupported in MessageReader"); - Future flush() => throw new UnsupportedError("Unsuppored in MessageReader"); + Future flush() => throw new UnsupportedError("Unsupported in MessageReader"); } From 24fec2938fb43bd264f704b3b5079c9533c7f99a Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Tue, 15 Sep 2015 10:52:02 -0500 Subject: [PATCH 31/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Add support for a timeout when waiting for a response. --- .../lib/src/transport/t_socket_transport.dart | 21 +++++++++++++++++-- .../transport/t_socket_transport_test.dart | 17 +++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/dart/lib/src/transport/t_socket_transport.dart b/lib/dart/lib/src/transport/t_socket_transport.dart index 276cf6eca77..8cdc27a848d 100644 --- a/lib/dart/lib/src/transport/t_socket_transport.dart +++ b/lib/dart/lib/src/transport/t_socket_transport.dart @@ -62,20 +62,37 @@ abstract class TSocketTransport extends TBufferedTransport { /// [TClientSocketTransport] sends outgoing messages and expects a response class TClientSocketTransport extends TSocketTransport { + static const defaultTimeout = const Duration(seconds: 30); + final Map> _completers = {}; final TMessageReader messageReader; - TClientSocketTransport(TSocket socket, TProtocolFactory protocolFactory) + final Duration responseTimeout; + + TClientSocketTransport(TSocket socket, TProtocolFactory protocolFactory, + {Duration responseTimeout: defaultTimeout}) : messageReader = new TMessageReader(protocolFactory), + this.responseTimeout = responseTimeout, super(socket); Future flush() { Uint8List bytes = _consumeWriteBuffer(); TMessage message = messageReader.readMessage(bytes); + int seqid = message.seqid; Completer completer = new Completer(); - _completers[message.seqid] = completer; + _completers[seqid] = completer; + + if (responseTimeout != null) { + new Future.delayed(responseTimeout, () { + var completer = _completers.remove(seqid); + if (completer != null) { + completer.completeError( + new TimeoutException("Response timed out.", responseTimeout)); + } + }); + } socket.send(bytes); diff --git a/lib/dart/test/transport/t_socket_transport_test.dart b/lib/dart/test/transport/t_socket_transport_test.dart index 05560043f17..5a6f4f48ae5 100644 --- a/lib/dart/test/transport/t_socket_transport_test.dart +++ b/lib/dart/test/transport/t_socket_transport_test.dart @@ -52,6 +52,23 @@ void main() { expect(bufferText, responseText); }); + + test('Test response timeout', () async { + var socket = new FakeSocket(); + await socket.open(); + + FakeProtocolFactory protocolFactory = new FakeProtocolFactory(); + protocolFactory.message = new TMessage("foo", TMessageType.CALL, 123); + var transport = new TClientSocketTransport(socket, protocolFactory, responseTimeout: Duration.ZERO); + transport.writeAll(requestBytes); + + Future responseReady = transport.flush(); + + expect(responseReady, throwsA(new isInstanceOf())); + + // allow the timeout to occur + await new Future.delayed(new Duration(milliseconds: 100)); + }); }, timeout: new Timeout(new Duration(seconds: 1))); group('TServerTransport', () { From f07181fd866aca1a4ebfdf1cf378a8630fe22100 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Tue, 15 Sep 2015 11:34:02 -0500 Subject: [PATCH 32/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Make TMessageReader not extend TTransport, move to internal class _TMessageReaderTransport --- .../lib/src/transport/t_message_reader.dart | 26 ++++++++++++++----- .../transport/t_socket_transport_test.dart | 3 --- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/dart/lib/src/transport/t_message_reader.dart b/lib/dart/lib/src/transport/t_message_reader.dart index cba101b9615..8c99523b4a4 100644 --- a/lib/dart/lib/src/transport/t_message_reader.dart +++ b/lib/dart/lib/src/transport/t_message_reader.dart @@ -19,22 +19,36 @@ part of thrift; /// [TMessageReader] extracts a [TMessage] from bytes. This is used to allow a /// transport to inspect the message seqid and map responses to requests. -class TMessageReader extends TTransport { +class TMessageReader { final TProtocolFactory protocolFactory; - TMessageReader(this.protocolFactory); + final _TMessageReaderTransport _transport; - Iterator _readIterator; + TMessageReader(this.protocolFactory) + : _transport = new _TMessageReaderTransport(); TMessage readMessage(Uint8List bytes) { - TProtocol protocol = protocolFactory.getProtocol(this); - _readIterator = bytes.iterator; + _transport.reset(bytes); + TProtocol protocol = protocolFactory.getProtocol(_transport); TMessage message = protocol.readMessageBegin(); - _readIterator = null; + _transport.reset(null); return message; } +} + +/// An internal class used to support [TMessageReader]. +class _TMessageReaderTransport extends TTransport { + + _TMessageReaderTransport(); + + Iterator _readIterator; + + void reset(Uint8List bytes) { + _readIterator = bytes != null ? bytes.iterator : null; + } + get isOpen => true; Future open() => throw new UnsupportedError("Unsupported in MessageReader"); diff --git a/lib/dart/test/transport/t_socket_transport_test.dart b/lib/dart/test/transport/t_socket_transport_test.dart index 5a6f4f48ae5..f009e9c28d5 100644 --- a/lib/dart/test/transport/t_socket_transport_test.dart +++ b/lib/dart/test/transport/t_socket_transport_test.dart @@ -65,9 +65,6 @@ void main() { Future responseReady = transport.flush(); expect(responseReady, throwsA(new isInstanceOf())); - - // allow the timeout to occur - await new Future.delayed(new Duration(milliseconds: 100)); }); }, timeout: new Timeout(new Duration(seconds: 1))); From 925d1f64c7dda8621ca5ebc121fd5ff6d5935093 Mon Sep 17 00:00:00 2001 From: evanweible-wf Date: Tue, 15 Sep 2015 15:27:09 -0500 Subject: [PATCH 33/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Use dart_dev for code formatting, tests, coverage, and adding license. --- lib/dart/.gitignore | 1 + lib/dart/LICENSE_HEADER | 16 ++++++++ .../lib/src/transport/t_message_reader.dart | 2 - lib/dart/pubspec.yaml | 15 ++++--- lib/dart/test/protocol/t_protocol_test.dart | 21 ++++++++-- lib/dart/test/t_application_error_test.dart | 21 +++++++++- .../test/transport/t_http_transport_test.dart | 40 +++++++++++++------ .../transport/t_socket_transport_test.dart | 30 +++++++++----- lib/dart/tool/dev.dart | 33 +++++++++++++++ 9 files changed, 142 insertions(+), 37 deletions(-) create mode 100644 lib/dart/LICENSE_HEADER create mode 100644 lib/dart/tool/dev.dart diff --git a/lib/dart/.gitignore b/lib/dart/.gitignore index acda527ef2a..87db33ec98b 100644 --- a/lib/dart/.gitignore +++ b/lib/dart/.gitignore @@ -2,3 +2,4 @@ packages .pub/ pubspec.lock +/coverage/ diff --git a/lib/dart/LICENSE_HEADER b/lib/dart/LICENSE_HEADER new file mode 100644 index 00000000000..4eacb643179 --- /dev/null +++ b/lib/dart/LICENSE_HEADER @@ -0,0 +1,16 @@ +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. diff --git a/lib/dart/lib/src/transport/t_message_reader.dart b/lib/dart/lib/src/transport/t_message_reader.dart index 8c99523b4a4..7290e873488 100644 --- a/lib/dart/lib/src/transport/t_message_reader.dart +++ b/lib/dart/lib/src/transport/t_message_reader.dart @@ -35,12 +35,10 @@ class TMessageReader { return message; } - } /// An internal class used to support [TMessageReader]. class _TMessageReaderTransport extends TTransport { - _TMessageReaderTransport(); Iterator _readIterator; diff --git a/lib/dart/pubspec.yaml b/lib/dart/pubspec.yaml index 142fbb4bffa..4df26f2666b 100644 --- a/lib/dart/pubspec.yaml +++ b/lib/dart/pubspec.yaml @@ -6,11 +6,14 @@ author: Mark Erickson homepage: https://github.com/apache/thrift documentation: https://github.com/apache/thrift environment: - sdk: ^1.12.0 + sdk: ">=1.12.0 <2.0.0" dependencies: - crypto: ^0.9.0 - http: ^0.11.3 - logging: ^0.11.0 + crypto: "^0.9.0" + http: "^0.11.3" + logging: "^0.11.0" dev_dependencies: - mockito: ^0.11.0 - test: ^0.12.0 + coverage: "^0.7.2" + dart_dev: "^1.0.1" + dart_style: "^0.2.0" + mockito: "^0.11.0" + test: "^0.12.0" diff --git a/lib/dart/test/protocol/t_protocol_test.dart b/lib/dart/test/protocol/t_protocol_test.dart index a9d9dce8b0f..86da6f4627b 100644 --- a/lib/dart/test/protocol/t_protocol_test.dart +++ b/lib/dart/test/protocol/t_protocol_test.dart @@ -1,3 +1,20 @@ +// 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. + library thrift.test.transport.t_json_protocol_test; import 'dart:typed_data' show Uint8List; @@ -6,7 +23,6 @@ import 'package:test/test.dart'; import 'package:thrift/thrift.dart'; void main() { - final message = new TMessage('my message', TMessageType.ONEWAY, 123); TProtocol protocol; @@ -192,7 +208,7 @@ void main() { test('Test string', () async { var input = 'There are only two hard things in computer science: ' - 'cache invalidation, naming things, and off-by-one errors.'; + 'cache invalidation, naming things, and off-by-one errors.'; protocol.writeString(input); protocol.writeMessageEnd(); @@ -238,5 +254,4 @@ void main() { group('shared tests', sharedTests); }); - } diff --git a/lib/dart/test/t_application_error_test.dart b/lib/dart/test/t_application_error_test.dart index 0563d794762..511d8d6918d 100644 --- a/lib/dart/test/t_application_error_test.dart +++ b/lib/dart/test/t_application_error_test.dart @@ -1,3 +1,20 @@ +// 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. + library thrift.test.t_application_error_test; import 'package:test/test.dart'; @@ -14,7 +31,8 @@ void main() { var expectedType = TApplicationErrorType.INTERNAL_ERROR; var expectedMessage = 'test error message'; - TApplicationError error = new TApplicationError(expectedType, expectedMessage); + TApplicationError error = + new TApplicationError(expectedType, expectedMessage); error.write(protocol); protocol.transport.flush(); @@ -25,5 +43,4 @@ void main() { expect(subject.type, expectedType); expect(subject.message, expectedMessage); }); - } diff --git a/lib/dart/test/transport/t_http_transport_test.dart b/lib/dart/test/transport/t_http_transport_test.dart index 32293c0966b..59513c4ee86 100644 --- a/lib/dart/test/transport/t_http_transport_test.dart +++ b/lib/dart/test/transport/t_http_transport_test.dart @@ -1,3 +1,20 @@ +// 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. + library thrift.test.transport.t_socket_transport_test; import 'dart:async'; @@ -14,7 +31,6 @@ import 'package:test/test.dart'; import 'package:thrift/thrift.dart'; void main() { - const utf8Codec = const Utf8Codec(); FakeHttpClient client; @@ -36,8 +52,8 @@ void main() { expect(client.postRequest, isNotEmpty); - var requestText = utf8Codec.decode( - CryptoUtils.base64StringToBytes(client.postRequest)); + var requestText = + utf8Codec.decode(CryptoUtils.base64StringToBytes(client.postRequest)); expect(requestText, expectedText); }); @@ -59,17 +75,14 @@ void main() { var bufferText = utf8Codec.decode(buffer); expect(bufferText, expectedText); }); - } - class FakeHttpClient implements Client { - String postResponse = ""; String postRequest = ""; - Future post(url, {Map headers, body, - Encoding encoding}) async { + Future post(url, + {Map headers, body, Encoding encoding}) async { postRequest = body; return new Response(postResponse, 200); } @@ -80,11 +93,13 @@ class FakeHttpClient implements Client { Future get(url, {Map headers}) => throw new UnimplementedError(); - Future put(url, {Map headers, body, - Encoding encoding}) => throw new UnimplementedError(); + Future put(url, + {Map headers, body, Encoding encoding}) => + throw new UnimplementedError(); - Future patch(url, {Map headers, body, - Encoding encoding}) => throw new UnimplementedError(); + Future patch(url, + {Map headers, body, Encoding encoding}) => + throw new UnimplementedError(); Future delete(url, {Map headers}) => throw new UnimplementedError(); @@ -99,5 +114,4 @@ class FakeHttpClient implements Client { throw new UnimplementedError(); void close() => throw new UnimplementedError(); - } diff --git a/lib/dart/test/transport/t_socket_transport_test.dart b/lib/dart/test/transport/t_socket_transport_test.dart index f009e9c28d5..58c0cd43da5 100644 --- a/lib/dart/test/transport/t_socket_transport_test.dart +++ b/lib/dart/test/transport/t_socket_transport_test.dart @@ -1,3 +1,20 @@ +// 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. + library thrift.test.transport.t_socket_transport_test; import 'dart:async'; @@ -10,7 +27,6 @@ import 'package:test/test.dart'; import 'package:thrift/thrift.dart'; void main() { - const utf8Codec = const Utf8Codec(); final requestText = 'my test request'; @@ -59,7 +75,8 @@ void main() { FakeProtocolFactory protocolFactory = new FakeProtocolFactory(); protocolFactory.message = new TMessage("foo", TMessageType.CALL, 123); - var transport = new TClientSocketTransport(socket, protocolFactory, responseTimeout: Duration.ZERO); + var transport = new TClientSocketTransport(socket, protocolFactory, + responseTimeout: Duration.ZERO); transport.writeAll(requestBytes); Future responseReady = transport.flush(); @@ -109,12 +126,9 @@ void main() { expect(socket.sendPayload, responseBytes); }); }, timeout: new Timeout(new Duration(seconds: 1))); - } - class FakeSocket extends TSocket { - final StreamController _onStateController; Stream get onState => _onStateController.stream; @@ -161,26 +175,20 @@ class FakeSocket extends TSocket { new Uint8List.fromList(CryptoUtils.base64StringToBytes(base64)); _onMessageController.add(message); } - } - class FakeProtocolFactory implements TProtocolFactory { - FakeProtocolFactory(); TMessage message; getProtocol(TTransport transport) => new FakeProtocol(message); - } class FakeProtocol extends Mock implements TProtocol { - FakeProtocol(this._message); TMessage _message; readMessageBegin() => _message; - } diff --git a/lib/dart/tool/dev.dart b/lib/dart/tool/dev.dart new file mode 100644 index 00000000000..27f8b8fcf6f --- /dev/null +++ b/lib/dart/tool/dev.dart @@ -0,0 +1,33 @@ +// 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. + +library tool.dev; + +import 'package:dart_dev/dart_dev.dart' show dev, config; + +main(List args) async { + // https://github.com/Workiva/dart_dev + + var directories = ['lib/', 'test/', 'tool/']; + config.analyze.entryPoints = directories; + config.format.directories = directories; + config.copyLicense + ..licensePath = 'LICENSE_HEADER' + ..directories = directories; + + await dev(args); +} From 0543d4a03b86fbaa498abd53a9b8e31a184f6aee Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Tue, 15 Sep 2015 16:24:49 -0500 Subject: [PATCH 34/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Remove unused generator argument from build.sh --- tutorial/dart/build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial/dart/build.sh b/tutorial/dart/build.sh index 505385e4c07..fcc4e069f67 100755 --- a/tutorial/dart/build.sh +++ b/tutorial/dart/build.sh @@ -3,12 +3,12 @@ set -e; rm -r gen-dart || true; -thrift --gen "dart:gen_server" "../shared.thrift"; +thrift --gen dart ../shared.thrift; cd gen-dart/shared; pub get; cd ../..; -thrift --gen "dart:gen_server" "../tutorial.thrift"; +thrift --gen dart ../tutorial.thrift; cd gen-dart/tutorial; pub get; cd ../..; From 26bcc6231134a222e4fec724e21b2be0ef098e58 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Thu, 17 Sep 2015 21:50:51 -0500 Subject: [PATCH 35/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Add support for generics in generated Maps, Sets, and Lists. Remove unused arguments from type_name and base_type_name. --- compiler/cpp/src/generate/t_dart_generator.cc | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/compiler/cpp/src/generate/t_dart_generator.cc b/compiler/cpp/src/generate/t_dart_generator.cc index 285b5c8c3c0..d492e55d72f 100644 --- a/compiler/cpp/src/generate/t_dart_generator.cc +++ b/compiler/cpp/src/generate/t_dart_generator.cc @@ -215,8 +215,8 @@ class t_dart_generator : public t_oop_generator { std::string dart_library(string file_name); std::string service_imports(); std::string dart_thrift_imports(); - std::string type_name(t_type* ttype, bool in_container = false, bool in_init = false); - std::string base_type_name(t_base_type* tbase, bool in_container = false); + std::string type_name(t_type* ttype); + std::string base_type_name(t_base_type* tbase); std::string declare_field(t_field* tfield, bool init = false); std::string function_signature(t_function* tfunction); std::string argument_list(t_struct* tstruct); @@ -515,7 +515,7 @@ void t_dart_generator::print_const_value(std::ofstream& out, vector::const_iterator f_iter; const map& val = value->get_map(); map::const_iterator v_iter; - out << type_name(type) << " " << name << " = new " << type_name(type, false, true) << "();" + out << type_name(type) << " " << name << " = new " << type_name(type) << "();" << endl; for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { t_type* field_type = NULL; @@ -562,7 +562,7 @@ void t_dart_generator::print_const_value(std::ofstream& out, out << "[" << endl; etype = ((t_list*)type)->get_elem_type(); } else { - out << "new " << type_name(type, false, true) << ".from([" << endl; + out << "new " << type_name(type) << ".from([" << endl; etype = ((t_set*)type)->get_elem_type(); } const vector& val = value->get_list(); @@ -1654,7 +1654,7 @@ void t_dart_generator::generate_process_function(t_service* tservice, t_function for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { string result_field_name = get_field_name((*x_iter)->get_name()); scope_down(f_service_, ""); - f_service_ << " on " << type_name((*x_iter)->get_type(), false, false) + f_service_ << " on " << type_name((*x_iter)->get_type()) << " catch(" << result_field_name << ")"; scope_up(f_service_); if (!tfunction->is_oneway()) { @@ -1794,7 +1794,7 @@ void t_dart_generator::generate_deserialize_container(ofstream& out, t_type* tty indent(out) << "TList " << obj << " = iprot.readListBegin();" << endl; } - indent(out) << prefix << " = new " << type_name(ttype, false, true) << "();" << endl; + indent(out) << prefix << " = new " << type_name(ttype) << "();" << endl; // For loop iterates over elements string i = tmp("_i"); @@ -2049,21 +2049,23 @@ void t_dart_generator::generate_serialize_list_element(ofstream& out, t_list* tl * @param container Is the type going inside a container? * @return Dart type name, i.e. HashMap */ -string t_dart_generator::type_name(t_type* ttype, bool in_container, bool in_init) { - (void)in_init; +string t_dart_generator::type_name(t_type* ttype) { ttype = get_true_type(ttype); - string prefix; if (ttype->is_base_type()) { - return base_type_name((t_base_type*)ttype, in_container); + return base_type_name((t_base_type*)ttype); } else if (ttype->is_enum()) { return "int"; } else if (ttype->is_map()) { - return "Map"; + t_map* tmap = (t_map*)ttype; + return "Map<" + type_name(tmap->get_key_type()) + ", " + + type_name(tmap->get_val_type()) + ">"; } else if (ttype->is_set()) { - return "Set"; + t_set* tset = (t_set*)ttype; + return "Set<" + type_name(tset->get_elem_type()) + ">"; } else if (ttype->is_list()) { - return "List"; + t_list* tlist = (t_list*)ttype; + return "List<" + type_name(tlist->get_elem_type()) + ">"; } return ttype->get_name(); @@ -2075,8 +2077,7 @@ string t_dart_generator::type_name(t_type* ttype, bool in_container, bool in_ini * @param tbase The base type * @param container Is it going in a Dart container? */ -string t_dart_generator::base_type_name(t_base_type* type, bool in_container) { - (void)in_container; +string t_dart_generator::base_type_name(t_base_type* type) { t_base_type::t_base tbase = type->get_base(); switch (tbase) { @@ -2140,9 +2141,9 @@ string t_dart_generator::declare_field(t_field* tfield, bool init) { } else if (ttype->is_enum()) { result += " = 0"; } else if (ttype->is_container()) { - result += " = new " + type_name(ttype, false, true) + "()"; + result += " = new " + type_name(ttype) + "()"; } else { - result += " = new " + type_name(ttype, false, true) + "()"; + result += " = new " + type_name(ttype) + "()"; ; } } From 4812ced6ed4357b8be92c5e33f8cf4fb8b4b0101 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Fri, 18 Sep 2015 08:55:06 -0500 Subject: [PATCH 36/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Fix comments --- compiler/cpp/src/generate/t_dart_generator.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/compiler/cpp/src/generate/t_dart_generator.cc b/compiler/cpp/src/generate/t_dart_generator.cc index d492e55d72f..33ab24cf814 100644 --- a/compiler/cpp/src/generate/t_dart_generator.cc +++ b/compiler/cpp/src/generate/t_dart_generator.cc @@ -2046,8 +2046,7 @@ void t_dart_generator::generate_serialize_list_element(ofstream& out, t_list* tl * Returns a Dart type name * * @param ttype The type - * @param container Is the type going inside a container? - * @return Dart type name, i.e. HashMap + * @return Dart type name, i.e. Map */ string t_dart_generator::type_name(t_type* ttype) { ttype = get_true_type(ttype); @@ -2075,7 +2074,6 @@ string t_dart_generator::type_name(t_type* ttype) { * Returns the Dart type that corresponds to the thrift type. * * @param tbase The base type - * @param container Is it going in a Dart container? */ string t_dart_generator::base_type_name(t_base_type* type) { t_base_type::t_base tbase = type->get_base(); From 082d1aa533367b14357e3b8f81a8f0ffe6cf0f6f Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Fri, 18 Sep 2015 13:49:16 -0500 Subject: [PATCH 37/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Add TTransportFactory class for future use in server implementation. --- .../src/transport/t_transport_factory.dart | 27 ++++++++++++ lib/dart/lib/thrift.dart | 1 + lib/dart/test/transport/t_transport_test.dart | 41 +++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 lib/dart/lib/src/transport/t_transport_factory.dart create mode 100644 lib/dart/test/transport/t_transport_test.dart diff --git a/lib/dart/lib/src/transport/t_transport_factory.dart b/lib/dart/lib/src/transport/t_transport_factory.dart new file mode 100644 index 00000000000..7a10461d2ff --- /dev/null +++ b/lib/dart/lib/src/transport/t_transport_factory.dart @@ -0,0 +1,27 @@ +/// 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. + +part of thrift; + +/// Factory class used to create wrapped instance of a [TTransport]. This is +/// used primarily in servers. +/// +/// Adapted from the Java version. +class TTransportFactory { + Future getTransport(TTransport transport) => + new Future.value(transport); +} diff --git a/lib/dart/lib/thrift.dart b/lib/dart/lib/thrift.dart index 4d67adf6b5c..2483726eee3 100644 --- a/lib/dart/lib/thrift.dart +++ b/lib/dart/lib/thrift.dart @@ -54,4 +54,5 @@ part 'src/transport/t_message_reader.dart'; part 'src/transport/t_socket.dart'; part 'src/transport/t_transport.dart'; part 'src/transport/t_transport_error.dart'; +part 'src/transport/t_transport_factory.dart'; part 'src/transport/t_socket_transport.dart'; diff --git a/lib/dart/test/transport/t_transport_test.dart b/lib/dart/test/transport/t_transport_test.dart new file mode 100644 index 00000000000..b3f3dde8e88 --- /dev/null +++ b/lib/dart/test/transport/t_transport_test.dart @@ -0,0 +1,41 @@ +// 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. + +library thrift.test.transport.t_socket_transport_test; + +import 'package:test/test.dart'; +import 'package:thrift/thrift.dart'; + +/// Common transport tests +void main() { + group('TTransportFactory', () { + test('transport is returned from base factory', () async { + TTransport result; + TTransport transport = null; + ; + var factory = new TTransportFactory(); + + result = await factory.getTransport(transport); + expect(result, isNull); + + transport = new TBufferedTransport(); + result = await factory.getTransport(transport); + + expect(result, transport); + }); + }); +} From 010799797cb8b4e3213ce6b54144eb56c0d9fc0c Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Fri, 18 Sep 2015 13:55:20 -0500 Subject: [PATCH 38/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Remove spurious semicolon. --- lib/dart/test/transport/t_transport_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dart/test/transport/t_transport_test.dart b/lib/dart/test/transport/t_transport_test.dart index b3f3dde8e88..0bb381ac833 100644 --- a/lib/dart/test/transport/t_transport_test.dart +++ b/lib/dart/test/transport/t_transport_test.dart @@ -26,7 +26,7 @@ void main() { test('transport is returned from base factory', () async { TTransport result; TTransport transport = null; - ; + var factory = new TTransportFactory(); result = await factory.getTransport(transport); From 43a69dbda7bc5c6cb13ee003732e9ffb324363f5 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Mon, 21 Sep 2015 09:26:15 -0500 Subject: [PATCH 39/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Add a Dart console-client to the tutorial. This was used to successfully run the tutorial with a Dart client and Python server using a TCP socket transport. --- lib/dart/lib/src/console/t_tcp_socket.dart | 81 ++++++++++++++ lib/dart/lib/src/console/t_web_socket.dart | 2 +- lib/dart/lib/thrift_console.dart | 1 + tutorial/dart/console-client/bin/main.dart | 119 +++++++++++++++++++++ tutorial/dart/console-client/pubspec.yaml | 17 +++ 5 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 lib/dart/lib/src/console/t_tcp_socket.dart create mode 100644 tutorial/dart/console-client/bin/main.dart create mode 100644 tutorial/dart/console-client/pubspec.yaml diff --git a/lib/dart/lib/src/console/t_tcp_socket.dart b/lib/dart/lib/src/console/t_tcp_socket.dart new file mode 100644 index 00000000000..b714803345f --- /dev/null +++ b/lib/dart/lib/src/console/t_tcp_socket.dart @@ -0,0 +1,81 @@ +/// 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. + +library thrift.src.console.t_tcp_socket; + +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data' show Uint8List; + +import 'package:thrift/thrift.dart'; + +/// A [TSocket] backed by a [Socket] from dart:io +class TTcpSocket implements TSocket { + final StreamController _onStateController; + Stream get onState => _onStateController.stream; + + final StreamController _onErrorController; + Stream get onError => _onErrorController.stream; + + final StreamController _onMessageController; + Stream get onMessage => _onMessageController.stream; + + TTcpSocket(Socket socket) + : _onStateController = new StreamController.broadcast(), + _onErrorController = new StreamController.broadcast(), + _onMessageController = new StreamController.broadcast() { + if (socket == null) { + throw new ArgumentError.notNull('socket'); + } + + _socket = socket; + _socket.listen(_onMessage, onError: _onError, onDone: close); + } + + Socket _socket; + + bool get isOpen => _socket != null; + + bool get isClosed => _socket == null; + + Future open() async { + _onStateController.add(TSocketState.OPEN); + } + + Future close() async { + if (_socket != null) { + await _socket.close(); + _socket = null; + } + + _onStateController.add(TSocketState.CLOSED); + } + + void send(Uint8List data) { + _socket.add(data); + } + + void _onMessage(List message) { + Uint8List data = new Uint8List.fromList(message); + _onMessageController.add(data); + } + + void _onError(Object error) { + close(); + _onErrorController.add('$error'); + } +} diff --git a/lib/dart/lib/src/console/t_web_socket.dart b/lib/dart/lib/src/console/t_web_socket.dart index fb9835524a1..5a549be2130 100644 --- a/lib/dart/lib/src/console/t_web_socket.dart +++ b/lib/dart/lib/src/console/t_web_socket.dart @@ -15,7 +15,7 @@ /// specific language governing permissions and limitations /// under the License. -library thrift.src.console; +library thrift.src.console.t_web_socket; import 'dart:async'; import 'dart:io'; diff --git a/lib/dart/lib/thrift_console.dart b/lib/dart/lib/thrift_console.dart index d5edbd30a45..48a83d1dc1e 100644 --- a/lib/dart/lib/thrift_console.dart +++ b/lib/dart/lib/thrift_console.dart @@ -19,4 +19,5 @@ library thrift_console; /// Classes that are only supported in console applications go here +export 'src/console/t_tcp_socket.dart' show TTcpSocket; export 'src/console/t_web_socket.dart' show TWebSocket; diff --git a/tutorial/dart/console-client/bin/main.dart b/tutorial/dart/console-client/bin/main.dart new file mode 100644 index 00000000000..e8c894b1c20 --- /dev/null +++ b/tutorial/dart/console-client/bin/main.dart @@ -0,0 +1,119 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:thrift/thrift.dart'; +import 'package:thrift/thrift_console.dart'; +import 'package:tutorial/tutorial.dart'; + +TTransport _transport; +Calculator _calculator; +int logid = 0; + +const Map operationLookup = const { + '+': Operation.ADD, + '-': Operation.SUBTRACT, + '*': Operation.MULTIPLY, + '/': Operation.DIVIDE +}; + +main(List args) { + Logger.root.level = Level.ALL; + Logger.root.onRecord.listen((LogRecord rec) { + print('${rec.level.name}: ${rec.time}: ${rec.message}'); + }); + + int port = 9090; + if (!args.isEmpty) { + port = int.parse(args[0]); + } + + _initConnection(port).then((_) => _run()); +} + +Future _initConnection(int port) async { + var socket = await Socket.connect('127.0.0.1', port); + _transport = new TClientSocketTransport(new TTcpSocket(socket), new TBinaryProtocolFactory()); + TProtocol protocol = new TBinaryProtocol(_transport); + await _transport.open(); + + _calculator = new CalculatorClient(protocol); +} + +Future _run() async { + _help(); + + while (true) { + stdout.write("> "); + var input = stdin.readLineSync(); + var parts = input.split(' '); + var command = parts[0]; + var args = parts.length > 1 ? parts.sublist(1) : []; + + switch (command) { + case 'ping': + await _ping(); + break; + + case 'add': + await _add(int.parse(args[0]), int.parse(args[1])); + break; + + case 'calc': + int op = operationLookup[args[1]]; + if (!Operation.VALID_VALUES.contains(op)) { + stdout.writeln('Unknown operator ${args[1]}'); + break; + } + + var work = new Work() + ..num1 = int.parse(args[0]) + ..op = op + ..num2 = int.parse(args[2]) + ..comment = args.length > 3 ? args[3] : ''; + + await _calc(work); + break; + + case 'struct': + await _struct(int.parse(args[0])); + break; + + case 'help': + default: + _help(); + break; + } + + } +} + +void _help() { + stdout.writeln('Commands:'); + stdout.writeln(' help'); + stdout.writeln(' ping'); + stdout.writeln(' add x y'); + stdout.writeln(' calc x op y [comment]'); + stdout.writeln(' struct id'); + stdout.writeln(''); +} + +Future _ping() async { + await _calculator.ping(); + stdout.writeln('ping succeeded'); +} + +Future _add(int x, int y) async { + int result = await _calculator.add(x, y); + stdout.writeln('= $result'); +} + +Future _calc(Work work) async { + int result = await _calculator.calculate(logid++, work); + stdout.writeln('= $result'); +} + +Future _struct(int key) async { + var struct = await _calculator.getStruct(key); + stdout.writeln(struct.toString()); +} diff --git a/tutorial/dart/console-client/pubspec.yaml b/tutorial/dart/console-client/pubspec.yaml new file mode 100644 index 00000000000..b44d9723441 --- /dev/null +++ b/tutorial/dart/console-client/pubspec.yaml @@ -0,0 +1,17 @@ +name: tutorial_console_client +version: 0.0.1 +description: > + A Dart console client to implementation of the Apache Thrift tutorial +author: Mark Erickson +homepage: https://github.com/apache/thrift + +environment: + sdk: ^1.12.0 + +dependencies: + shared: + path: ../gen-dart/shared + thrift: + path: ../../../lib/dart + tutorial: + path: ../gen-dart/tutorial From eaa54dedb5aa92117515b8cf5a9f4cf0ae1d6a00 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Mon, 21 Sep 2015 09:34:06 -0500 Subject: [PATCH 40/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Add license files and fomatted. --- tutorial/dart/console-client/bin/main.dart | 29 +++++++++++++++++----- tutorial/dart/server/bin/main.dart | 17 +++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/tutorial/dart/console-client/bin/main.dart b/tutorial/dart/console-client/bin/main.dart index e8c894b1c20..38ec1fd5e49 100644 --- a/tutorial/dart/console-client/bin/main.dart +++ b/tutorial/dart/console-client/bin/main.dart @@ -1,3 +1,20 @@ +/// 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. + import 'dart:async'; import 'dart:io'; @@ -33,7 +50,8 @@ main(List args) { Future _initConnection(int port) async { var socket = await Socket.connect('127.0.0.1', port); - _transport = new TClientSocketTransport(new TTcpSocket(socket), new TBinaryProtocolFactory()); + _transport = new TClientSocketTransport( + new TTcpSocket(socket), new TBinaryProtocolFactory()); TProtocol protocol = new TBinaryProtocol(_transport); await _transport.open(); @@ -67,10 +85,10 @@ Future _run() async { } var work = new Work() - ..num1 = int.parse(args[0]) - ..op = op - ..num2 = int.parse(args[2]) - ..comment = args.length > 3 ? args[3] : ''; + ..num1 = int.parse(args[0]) + ..op = op + ..num2 = int.parse(args[2]) + ..comment = args.length > 3 ? args[3] : ''; await _calc(work); break; @@ -84,7 +102,6 @@ Future _run() async { _help(); break; } - } } diff --git a/tutorial/dart/server/bin/main.dart b/tutorial/dart/server/bin/main.dart index 736b6c6f71b..4cf015bc33b 100644 --- a/tutorial/dart/server/bin/main.dart +++ b/tutorial/dart/server/bin/main.dart @@ -1,3 +1,20 @@ +/// 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. + import 'dart:async'; import 'dart:io'; From e041af1df7c48cf1d02ecb7544d188cc28c53a5f Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Mon, 21 Sep 2015 16:15:10 -0500 Subject: [PATCH 41/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Use a sync completer to ensure that the buffer can be read immediately after the read buffer is set, and avoid a race condition where another response could overwrite the read buffer. --- .../lib/src/transport/t_http_transport.dart | 36 ++++++++++++------- .../lib/src/transport/t_socket_transport.dart | 5 ++- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/lib/dart/lib/src/transport/t_http_transport.dart b/lib/dart/lib/src/transport/t_http_transport.dart index 401ae0da050..0e99a1dfece 100644 --- a/lib/dart/lib/src/transport/t_http_transport.dart +++ b/lib/dart/lib/src/transport/t_http_transport.dart @@ -43,21 +43,31 @@ class THttpClientTransport extends TBufferedTransport { httpClient.close(); } - Future flush() async { + Future flush() { var requestBody = CryptoUtils.bytesToBase64(_consumeWriteBuffer()); - var response = await httpClient.post(config.url, - headers: config.headers, body: requestBody); - - Uint8List data; - try { - data = new Uint8List.fromList( - CryptoUtils.base64StringToBytes(response.body)); - } on FormatException catch (_) { - throw new TProtocolError(TProtocolErrorType.INVALID_DATA, - "Expected a Base 64 encoded string."); - } - _setReadBuffer(data); + // Use a sync completer to ensure that the buffer can be read immediately + // after the read buffer is set, and avoid a race condition where another + // response could overwrite the read buffer. + var completer = new Completer.sync(); + + httpClient + .post(config.url, headers: config.headers, body: requestBody) + .then((response) { + Uint8List data; + try { + data = new Uint8List.fromList( + CryptoUtils.base64StringToBytes(response.body)); + } on FormatException catch (_) { + throw new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Expected a Base 64 encoded string."); + } + + _setReadBuffer(data); + completer.complete(); + }); + + return completer.future; } } diff --git a/lib/dart/lib/src/transport/t_socket_transport.dart b/lib/dart/lib/src/transport/t_socket_transport.dart index 8cdc27a848d..c7939fb20df 100644 --- a/lib/dart/lib/src/transport/t_socket_transport.dart +++ b/lib/dart/lib/src/transport/t_socket_transport.dart @@ -81,7 +81,10 @@ class TClientSocketTransport extends TSocketTransport { TMessage message = messageReader.readMessage(bytes); int seqid = message.seqid; - Completer completer = new Completer(); + // Use a sync completer to ensure that the buffer can be read immediately + // after the read buffer is set, and avoid a race condition where another + // response could overwrite the read buffer. + Completer completer = new Completer.sync(); _completers[seqid] = completer; if (responseTimeout != null) { From da7f5c9c2cc7e20a20ae308d178b55dd3ef12487 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Mon, 21 Sep 2015 17:12:10 -0500 Subject: [PATCH 42/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Change to BinaryProtocol to make tutorial clients more interchangeable. Add support in the tutorial server for TCP socket connections, in addition to WebSockets. --- tutorial/dart/build.sh | 6 +++ tutorial/dart/client/web/client.dart | 4 +- tutorial/dart/server/bin/main.dart | 59 +++++++++++++++++++--------- 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/tutorial/dart/build.sh b/tutorial/dart/build.sh index fcc4e069f67..37cc59fedd1 100755 --- a/tutorial/dart/build.sh +++ b/tutorial/dart/build.sh @@ -17,6 +17,10 @@ cd client; pub get; cd ..; +cd console-client; +pub get; +cd ..; + cd server; pub get; cd ..; @@ -30,4 +34,6 @@ echo "\nTo run the client:"; echo "# Serve the app from the client directory and view in a browser"; echo "> cd client;"; echo "> pub serve;"; +echo "\nTo run the console client:"; +echo "> dart console-client/bin/main.dart"; echo ""; diff --git a/tutorial/dart/client/web/client.dart b/tutorial/dart/client/web/client.dart index ae432c5b91e..fb64a42c526 100644 --- a/tutorial/dart/client/web/client.dart +++ b/tutorial/dart/client/web/client.dart @@ -48,8 +48,8 @@ class CalculatorUI { void _initConnection() { _transport = new TClientSocketTransport( - new TWebSocket(Uri.parse('ws://127.0.0.1:9090/ws')), new TJsonProtocolFactory()); - TProtocol protocol = new TJsonProtocol(_transport); + new TWebSocket(Uri.parse('ws://127.0.0.1:9090/ws')), new TBinaryProtocolFactory()); + TProtocol protocol = new TBinaryProtocol(_transport); _transport.open(); _calculatorClient = new CalculatorClient(protocol); diff --git a/tutorial/dart/server/bin/main.dart b/tutorial/dart/server/bin/main.dart index 4cf015bc33b..253950e619d 100644 --- a/tutorial/dart/server/bin/main.dart +++ b/tutorial/dart/server/bin/main.dart @@ -35,38 +35,59 @@ main(List args) { }); int port = 9090; - if (!args.isEmpty) { - port = int.parse(args[0]); + if (args.length > 0) { + try { + port = int.parse(args[0]); + } catch(e) { + print('dart server/bin/main.dart [port socketType]'); + print(' port .......... : port to listen on'); + print(' socket type ... : type of socket, one of [ws, tcp]'); + exit(0); + } + } + + String socketType; + if (args.length > 1) { + socketType = args[1]; } - _runServer(port); + if (socketType == 'tcp') { + _runTcpServer(port); + } else { + _runWebSocketServer(port); + } } -Future _runServer(int port) async { +Future _runWebSocketServer(int port) async { var httpServer = await HttpServer.bind('127.0.0.1', port); - httpServer.listen((HttpRequest request) => _handleRequest(request)); - - print('listening for connections on $port'); + print('listening for WebSocket connections on $port'); + + httpServer.listen((HttpRequest request) async { + if (request.uri.path == '/ws') { + _webSocket = await WebSocketTransformer.upgrade(request); + await _initProcessor(new TWebSocket(_webSocket)); + } else { + print('Invalid path: ${request.uri.path}'); + } + }); } -Future _handleRequest(HttpRequest request) async { - if (request.uri.path == '/ws') { - await _initWebSocket(request); - } else { - print('Invalid path: ${request.uri.path}'); - } -} +Future _runTcpServer(int port) async { + var serverSocket = await ServerSocket.bind('127.0.0.1', port); + print('listening for TCP connections on $port'); -Future _initWebSocket(HttpRequest request) async { - _webSocket = await WebSocketTransformer.upgrade(request); + Socket socket = await serverSocket.first; + await _initProcessor(new TTcpSocket(socket)); +} - TWebSocket socket = new TWebSocket(_webSocket); +Future _initProcessor(TSocket socket) async { TServerSocketTransport transport = new TServerSocketTransport(socket); transport.onIncomingMessage.listen(_processMessage); - _processor = new CalculatorProcessor(new CalculatorServer()); - _protocol = new TJsonProtocol(transport); + _protocol = new TBinaryProtocol(transport); await _protocol.transport.open(); + + print('connected'); } Future _processMessage(_) async { From 2cdcee019f89228991993492958fddce502d9f62 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Mon, 21 Sep 2015 18:18:57 -0500 Subject: [PATCH 43/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Make console_client conform naming more consistent --- tutorial/dart/build.sh | 4 ++-- .../dart/{console-client => console_client}/bin/main.dart | 0 tutorial/dart/{console-client => console_client}/pubspec.yaml | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename tutorial/dart/{console-client => console_client}/bin/main.dart (100%) rename tutorial/dart/{console-client => console_client}/pubspec.yaml (100%) diff --git a/tutorial/dart/build.sh b/tutorial/dart/build.sh index 37cc59fedd1..198b9f0cd57 100755 --- a/tutorial/dart/build.sh +++ b/tutorial/dart/build.sh @@ -17,7 +17,7 @@ cd client; pub get; cd ..; -cd console-client; +cd console_client; pub get; cd ..; @@ -35,5 +35,5 @@ echo "# Serve the app from the client directory and view in a browser"; echo "> cd client;"; echo "> pub serve;"; echo "\nTo run the console client:"; -echo "> dart console-client/bin/main.dart"; +echo "> dart console_client/bin/main.dart"; echo ""; diff --git a/tutorial/dart/console-client/bin/main.dart b/tutorial/dart/console_client/bin/main.dart similarity index 100% rename from tutorial/dart/console-client/bin/main.dart rename to tutorial/dart/console_client/bin/main.dart diff --git a/tutorial/dart/console-client/pubspec.yaml b/tutorial/dart/console_client/pubspec.yaml similarity index 100% rename from tutorial/dart/console-client/pubspec.yaml rename to tutorial/dart/console_client/pubspec.yaml From 52ebfa603e2369b5c2d460cd532465de6bbc2b43 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Mon, 21 Sep 2015 22:23:50 -0500 Subject: [PATCH 44/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Use args package to parse command line arguments. --- tutorial/dart/console_client/bin/main.dart | 19 ++++++++++-- tutorial/dart/console_client/pubspec.yaml | 1 + tutorial/dart/server/bin/main.dart | 34 +++++++++++++--------- tutorial/dart/server/pubspec.yaml | 1 + 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/tutorial/dart/console_client/bin/main.dart b/tutorial/dart/console_client/bin/main.dart index 38ec1fd5e49..7cd4d8a9c00 100644 --- a/tutorial/dart/console_client/bin/main.dart +++ b/tutorial/dart/console_client/bin/main.dart @@ -18,6 +18,7 @@ import 'dart:async'; import 'dart:io'; +import 'package:args/args.dart'; import 'package:logging/logging.dart'; import 'package:thrift/thrift.dart'; import 'package:thrift/thrift_console.dart'; @@ -40,11 +41,23 @@ main(List args) { print('${rec.level.name}: ${rec.time}: ${rec.message}'); }); - int port = 9090; - if (!args.isEmpty) { - port = int.parse(args[0]); + var parser = new ArgParser(); + parser.addOption('port', defaultsTo: '9090', help: 'The port to connect to'); + + ArgResults results; + try { + results = parser.parse(args); + } catch (e) { + results = null; + } + + if (results == null) { + print(parser.usage); + exit(0); } + int port = int.parse(results['port']); + _initConnection(port).then((_) => _run()); } diff --git a/tutorial/dart/console_client/pubspec.yaml b/tutorial/dart/console_client/pubspec.yaml index b44d9723441..a11121ab66e 100644 --- a/tutorial/dart/console_client/pubspec.yaml +++ b/tutorial/dart/console_client/pubspec.yaml @@ -9,6 +9,7 @@ environment: sdk: ^1.12.0 dependencies: + args: ^0.13.0 shared: path: ../gen-dart/shared thrift: diff --git a/tutorial/dart/server/bin/main.dart b/tutorial/dart/server/bin/main.dart index 253950e619d..0f2bb1e4a93 100644 --- a/tutorial/dart/server/bin/main.dart +++ b/tutorial/dart/server/bin/main.dart @@ -18,6 +18,7 @@ import 'dart:async'; import 'dart:io'; +import 'package:args/args.dart'; import 'package:logging/logging.dart'; import 'package:thrift/thrift.dart'; import 'package:thrift/thrift_console.dart'; @@ -34,26 +35,31 @@ main(List args) { print('${rec.level.name}: ${rec.time}: ${rec.message}'); }); - int port = 9090; - if (args.length > 0) { - try { - port = int.parse(args[0]); - } catch(e) { - print('dart server/bin/main.dart [port socketType]'); - print(' port .......... : port to listen on'); - print(' socket type ... : type of socket, one of [ws, tcp]'); - exit(0); - } + var parser = new ArgParser(); + parser.addOption('port', defaultsTo: '9090', help: 'The port to listen on'); + parser.addOption('type', defaultsTo: 'ws', allowed: ['ws', 'tcp'], help: 'The type of socket', allowedHelp: { + 'ws': 'WebSocket', + 'tcp': 'TCP Socket' + }); + + ArgResults results; + try { + results = parser.parse(args); + } catch (e) { + results = null; } - String socketType; - if (args.length > 1) { - socketType = args[1]; + if (results == null) { + print(parser.usage); + exit(0); } + int port = int.parse(results['port']); + String socketType = results['type']; + if (socketType == 'tcp') { _runTcpServer(port); - } else { + } else if (socketType == 'ws') { _runWebSocketServer(port); } } diff --git a/tutorial/dart/server/pubspec.yaml b/tutorial/dart/server/pubspec.yaml index b28f3dc1245..744f09a9a4e 100644 --- a/tutorial/dart/server/pubspec.yaml +++ b/tutorial/dart/server/pubspec.yaml @@ -8,6 +8,7 @@ environment: sdk: ^1.12.0 dependencies: + args: ^0.13.0 shared: path: ../gen-dart/shared thrift: From c695faa6d88f7292ca20288a6a612fffbe9a6ac0 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Mon, 21 Sep 2015 22:33:22 -0500 Subject: [PATCH 45/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Formatting. --- tutorial/dart/server/bin/main.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tutorial/dart/server/bin/main.dart b/tutorial/dart/server/bin/main.dart index 0f2bb1e4a93..b8ac30d3d76 100644 --- a/tutorial/dart/server/bin/main.dart +++ b/tutorial/dart/server/bin/main.dart @@ -37,10 +37,11 @@ main(List args) { var parser = new ArgParser(); parser.addOption('port', defaultsTo: '9090', help: 'The port to listen on'); - parser.addOption('type', defaultsTo: 'ws', allowed: ['ws', 'tcp'], help: 'The type of socket', allowedHelp: { - 'ws': 'WebSocket', - 'tcp': 'TCP Socket' - }); + parser.addOption('type', + defaultsTo: 'ws', + allowed: ['ws', 'tcp'], + help: 'The type of socket', + allowedHelp: {'ws': 'WebSocket', 'tcp': 'TCP Socket'}); ArgResults results; try { From 4833be75014c2bdf06478a7ea8897a8f2920fa52 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Tue, 22 Sep 2015 10:42:52 -0500 Subject: [PATCH 46/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Wrote tests to cover sync completers. --- .../test/transport/t_http_transport_test.dart | 114 +++++++++++++----- .../transport/t_socket_transport_test.dart | 79 +++++++++--- 2 files changed, 140 insertions(+), 53 deletions(-) diff --git a/lib/dart/test/transport/t_http_transport_test.dart b/lib/dart/test/transport/t_http_transport_test.dart index 59513c4ee86..7fcab3eae4a 100644 --- a/lib/dart/test/transport/t_http_transport_test.dart +++ b/lib/dart/test/transport/t_http_transport_test.dart @@ -33,58 +33,106 @@ import 'package:thrift/thrift.dart'; void main() { const utf8Codec = const Utf8Codec(); - FakeHttpClient client; - THttpClientTransport transport; + group('THttpClientTransport', () { + FakeHttpClient client; + THttpClientTransport transport; - setUp(() { - client = new FakeHttpClient(); - var config = new THttpConfig(Uri.parse("http://localhost"), {}); - transport = new THttpClientTransport(client, config); - }); + setUp(() { + client = new FakeHttpClient(sync: false); + var config = new THttpConfig(Uri.parse('http://localhost'), {}); + transport = new THttpClientTransport(client, config); + }); - test('Test transport sends body', () async { - var expectedText = "my request"; - transport.writeAll(utf8Codec.encode(expectedText)); + test('Test transport sends body', () async { + var expectedText = 'my request'; + transport.writeAll(utf8Codec.encode(expectedText)); - expect(client.postRequest, isEmpty); + expect(client.postRequest, isEmpty); - await transport.flush(); + await transport.flush(); - expect(client.postRequest, isNotEmpty); + expect(client.postRequest, isNotEmpty); - var requestText = - utf8Codec.decode(CryptoUtils.base64StringToBytes(client.postRequest)); - expect(requestText, expectedText); - }); + var requestText = + utf8Codec.decode(CryptoUtils.base64StringToBytes(client.postRequest)); + expect(requestText, expectedText); + }); - test('Test transport receives response', () async { - var expectedText = "my response"; - var expectedBytes = utf8Codec.encode(expectedText); - client.postResponse = CryptoUtils.bytesToBase64(expectedBytes); + test('Test transport receives response', () async { + var expectedText = 'my response'; + var expectedBytes = utf8Codec.encode(expectedText); + client.postResponse = CryptoUtils.bytesToBase64(expectedBytes); - transport.writeAll(utf8Codec.encode("my request")); - expect(transport.hasReadData, isFalse); + transport.writeAll(utf8Codec.encode('my request')); + expect(transport.hasReadData, isFalse); - await transport.flush(); + await transport.flush(); - expect(transport.hasReadData, isTrue); + expect(transport.hasReadData, isTrue); - var buffer = new Uint8List(expectedBytes.length); - transport.readAll(buffer, 0, expectedBytes.length); + var buffer = new Uint8List(expectedBytes.length); + transport.readAll(buffer, 0, expectedBytes.length); + + var bufferText = utf8Codec.decode(buffer); + expect(bufferText, expectedText); + }); + }); - var bufferText = utf8Codec.decode(buffer); - expect(bufferText, expectedText); + group('THttpClientTransport with multiple messages', () { + FakeHttpClient client; + THttpClientTransport transport; + + setUp(() { + client = new FakeHttpClient(sync: true); + var config = new THttpConfig(Uri.parse('http://localhost'), {}); + transport = new THttpClientTransport(client, config); + }); + + test('Test read correct buffer after flush', () async { + String bufferText; + var expectedText = 'response 1'; + var expectedBytes = utf8Codec.encode(expectedText); + + // prepare a response + transport.writeAll(utf8Codec.encode('request 1')); + client.postResponse = CryptoUtils.bytesToBase64(expectedBytes); + + Future responseReady = transport.flush().then((_) { + var buffer = new Uint8List(expectedBytes.length); + transport.readAll(buffer, 0, expectedBytes.length); + bufferText = utf8Codec.decode(buffer); + }); + + // prepare a second response + transport.writeAll(utf8Codec.encode('request 2')); + var response2Bytes = utf8Codec.encode('response 2'); + client.postResponse = CryptoUtils.bytesToBase64(response2Bytes); + await transport.flush(); + + await responseReady; + expect(bufferText, expectedText); + }); }); } class FakeHttpClient implements Client { - String postResponse = ""; - String postRequest = ""; + String postResponse = ''; + String postRequest = ''; + + final bool sync; + + FakeHttpClient({this.sync: false}); Future post(url, - {Map headers, body, Encoding encoding}) async { + {Map headers, body, Encoding encoding}) { postRequest = body; - return new Response(postResponse, 200); + var response = new Response(postResponse, 200); + + if (sync) { + return new Future.sync(() => response); + } else { + return new Future.value(response); + } } Future head(url, {Map headers}) => diff --git a/lib/dart/test/transport/t_socket_transport_test.dart b/lib/dart/test/transport/t_socket_transport_test.dart index 58c0cd43da5..caa4a3c34a4 100644 --- a/lib/dart/test/transport/t_socket_transport_test.dart +++ b/lib/dart/test/transport/t_socket_transport_test.dart @@ -33,20 +33,27 @@ void main() { final requestBytes = new Uint8List.fromList(utf8Codec.encode(requestText)); final requestBase64 = CryptoUtils.bytesToBase64(requestBytes); - final responseText = 'a response!'; + final responseText = 'response 1'; final responseBytes = new Uint8List.fromList(utf8Codec.encode(responseText)); final responseBase64 = CryptoUtils.bytesToBase64(responseBytes); group('TClientTransport', () { - test('Test client sending data over transport', () async { - var socket = new FakeSocket(); - await socket.open(); + FakeSocket socket; + FakeProtocolFactory protocolFactory; + TTransport transport; - FakeProtocolFactory protocolFactory = new FakeProtocolFactory(); - protocolFactory.message = new TMessage("foo", TMessageType.CALL, 123); - var transport = new TClientSocketTransport(socket, protocolFactory); + setUp(() async { + socket = new FakeSocket(sync: false); + await socket.open(); + protocolFactory = new FakeProtocolFactory(); + protocolFactory.message = new TMessage('foo', TMessageType.CALL, 123); + transport = new TClientSocketTransport(socket, protocolFactory, + responseTimeout: Duration.ZERO); transport.writeAll(requestBytes); + }); + + test('Test client sending data over transport', () async { expect(socket.sendPayload, isNull); Future responseReady = transport.flush(); @@ -58,7 +65,7 @@ void main() { expect(socket.sendPayload, requestBytes); // simulate a response - protocolFactory.message = new TMessage("foo", TMessageType.REPLY, 123); + protocolFactory.message = new TMessage('foo', TMessageType.REPLY, 123); socket.receiveFakeMessage(responseBase64); await responseReady; @@ -70,18 +77,50 @@ void main() { }); test('Test response timeout', () async { - var socket = new FakeSocket(); + Future responseReady = transport.flush(); + expect(responseReady, throwsA(new isInstanceOf())); + }); + }, timeout: new Timeout(new Duration(seconds: 1))); + + group('TClientTransport with multiple messages', () { + FakeSocket socket; + FakeProtocolFactory protocolFactory; + TTransport transport; + + setUp(() async { + socket = new FakeSocket(sync: true); await socket.open(); - FakeProtocolFactory protocolFactory = new FakeProtocolFactory(); - protocolFactory.message = new TMessage("foo", TMessageType.CALL, 123); - var transport = new TClientSocketTransport(socket, protocolFactory, + protocolFactory = new FakeProtocolFactory(); + protocolFactory.message = new TMessage('foo', TMessageType.CALL, 123); + transport = new TClientSocketTransport(socket, protocolFactory, responseTimeout: Duration.ZERO); transport.writeAll(requestBytes); + }); - Future responseReady = transport.flush(); + test('Test read correct buffer after flush', () async { + String bufferText; - expect(responseReady, throwsA(new isInstanceOf())); + Future responseReady = transport.flush().then((_) { + var buffer = new Uint8List(responseBytes.length); + transport.readAll(buffer, 0, responseBytes.length); + bufferText = utf8Codec.decode(buffer); + }); + + // simulate a response + protocolFactory.message = new TMessage('foo', TMessageType.REPLY, 123); + socket.receiveFakeMessage(responseBase64); + + // simulate a second response + var response2Text = 'response 2'; + var response2Bytes = + new Uint8List.fromList(utf8Codec.encode(response2Text)); + var response2Base64 = CryptoUtils.bytesToBase64(response2Bytes); + protocolFactory.message = new TMessage('foo2', TMessageType.REPLY, 124); + socket.receiveFakeMessage(response2Base64); + + await responseReady; + expect(bufferText, responseText); }); }, timeout: new Timeout(new Duration(seconds: 1))); @@ -138,10 +177,10 @@ class FakeSocket extends TSocket { final StreamController _onMessageController; Stream get onMessage => _onMessageController.stream; - FakeSocket() - : _onStateController = new StreamController.broadcast(), - _onErrorController = new StreamController.broadcast(), - _onMessageController = new StreamController.broadcast(); + FakeSocket({bool sync: false}) + : _onStateController = new StreamController.broadcast(sync: sync), + _onErrorController = new StreamController.broadcast(sync: sync), + _onMessageController = new StreamController.broadcast(sync: sync); bool _isOpen; @@ -163,13 +202,13 @@ class FakeSocket extends TSocket { Uint8List get sendPayload => _sendPayload; void send(Uint8List data) { - if (!isOpen) throw new StateError("The socket is not open"); + if (!isOpen) throw new StateError('The socket is not open'); _sendPayload = data; } void receiveFakeMessage(String base64) { - if (!isOpen) throw new StateError("The socket is not open"); + if (!isOpen) throw new StateError('The socket is not open'); var message = new Uint8List.fromList(CryptoUtils.base64StringToBytes(base64)); From cad3d53cc15857037c992a449552dead81af1a08 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Tue, 22 Sep 2015 13:37:24 -0500 Subject: [PATCH 47/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Coerce null primitives to default values on write. --- .../lib/src/protocol/t_binary_protocol.dart | 8 +- .../lib/src/protocol/t_json_protocol.dart | 8 +- lib/dart/test/protocol/t_protocol_test.dart | 159 ++++++++++-------- 3 files changed, 104 insertions(+), 71 deletions(-) diff --git a/lib/dart/lib/src/protocol/t_binary_protocol.dart b/lib/dart/lib/src/protocol/t_binary_protocol.dart index acaf3bb831b..f73223c596c 100644 --- a/lib/dart/lib/src/protocol/t_binary_protocol.dart +++ b/lib/dart/lib/src/protocol/t_binary_protocol.dart @@ -99,41 +99,47 @@ class TBinaryProtocol extends TProtocol { void writeSetEnd() {} void writeBool(bool b) { + if (b == null) b = false; writeByte(b ? 1 : 0); } final ByteData _byteOut = new ByteData(1); void writeByte(int byte) { + if (byte == null) byte = 0; _byteOut.setUint8(0, byte); transport.write(_byteOut.buffer.asUint8List(), 0, 1); } final ByteData _i16Out = new ByteData(2); void writeI16(int i16) { + if (i16 == null) i16 = 0; _i16Out.setInt16(0, i16); transport.write(_i16Out.buffer.asUint8List(), 0, 2); } final ByteData _i32Out = new ByteData(4); void writeI32(int i32) { + if (i32 == null) i32 = 0; _i32Out.setInt32(0, i32); transport.write(_i32Out.buffer.asUint8List(), 0, 4); } final ByteData _i64Out = new ByteData(8); void writeI64(int i64) { + if (i64 == null) i64 = 0; _i64Out.setInt64(0, i64); transport.write(_i64Out.buffer.asUint8List(), 0, 8); } void writeString(String s) { - var bytes = _utf8Codec.encode(s); + var bytes = s != null ? _utf8Codec.encode(s) : new Uint8List.fromList([]); writeI32(bytes.length); transport.write(bytes, 0, bytes.length); } final ByteData _doubleOut = new ByteData(8); void writeDouble(double d) { + if (d == null) d = 0.0; _doubleOut.setFloat64(0, d); transport.write(_doubleOut.buffer.asUint8List(), 0, 8); } diff --git a/lib/dart/lib/src/protocol/t_json_protocol.dart b/lib/dart/lib/src/protocol/t_json_protocol.dart index e644a636822..805e937d511 100644 --- a/lib/dart/lib/src/protocol/t_json_protocol.dart +++ b/lib/dart/lib/src/protocol/t_json_protocol.dart @@ -117,6 +117,8 @@ class TJsonProtocol extends TProtocol { } void _writeJsonInteger(int i) { + if (i == null) i = 0; + _context.write(); String str = i.toString(); @@ -130,6 +132,8 @@ class TJsonProtocol extends TProtocol { } void _writeJsonDouble(double d) { + if (d == null) d = 0.0; + _context.write(); String str = d.toString(); bool escapeNumbers = d.isNaN || d.isInfinite || _context.escapeNumbers; @@ -244,6 +248,7 @@ class TJsonProtocol extends TProtocol { } void writeBool(bool b) { + if (b == null) b = false; _writeJsonInteger(b ? 1 : 0); } @@ -268,7 +273,8 @@ class TJsonProtocol extends TProtocol { } void writeString(String s) { - _writeJsonString(utf8Codec.encode(s)); + var bytes = s != null ? utf8Codec.encode(s) : new Uint8List.fromList([]); + _writeJsonString(bytes); } void writeBinary(Uint8List bytes) { diff --git a/lib/dart/test/protocol/t_protocol_test.dart b/lib/dart/test/protocol/t_protocol_test.dart index 86da6f4627b..3b65e0e5dd4 100644 --- a/lib/dart/test/protocol/t_protocol_test.dart +++ b/lib/dart/test/protocol/t_protocol_test.dart @@ -17,6 +17,7 @@ library thrift.test.transport.t_json_protocol_test; +import 'dart:async'; import 'dart:typed_data' show Uint8List; import 'package:test/test.dart'; @@ -27,6 +28,58 @@ void main() { TProtocol protocol; + Primitive getPrimitive(int tType) { + switch (tType) { + case TType.BOOL: + return new Primitive(protocol.readBool, protocol.writeBool, false); + + case TType.BYTE: + return new Primitive(protocol.readByte, protocol.writeByte, 0); + + case TType.I16: + return new Primitive(protocol.readI16, protocol.writeI16, 0); + + case TType.I32: + return new Primitive(protocol.readI32, protocol.writeI32, 0); + + case TType.I64: + return new Primitive(protocol.readI64, protocol.writeI64, 0); + + case TType.DOUBLE: + return new Primitive(protocol.readDouble, protocol.writeDouble, 0); + + case TType.STRING: + return new Primitive(protocol.readString, protocol.writeString, ''); + + default: + throw new UnsupportedError("Unsupported TType $tType"); + } + } + + Future primitiveTest(Primitive primitive, input) async { + primitive.write(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = primitive.read(); + + expect(output, input); + } + + Future primitiveNullTest(Primitive primitive) async { + primitive.write(null); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = primitive.read(); + + expect(output, primitive.defaultValue); + } + var sharedTests = () { test('Test message', () async { protocol.writeMessageEnd(); @@ -123,102 +176,61 @@ void main() { }); test('Test bool', () async { - var input = true; - - protocol.writeBool(input); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readBool(); + await primitiveTest(getPrimitive(TType.BOOL), true); + }); - expect(output, input); + test('Test bool null', () async { + await primitiveNullTest(getPrimitive(TType.BOOL)); }); test('Test byte', () async { - var input = 64; - - protocol.writeByte(input); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readByte(); + await primitiveTest(getPrimitive(TType.BYTE), 64); + }); - expect(output, input); + test('Test byte null', () async { + await primitiveNullTest(getPrimitive(TType.BYTE)); }); test('Test I16', () async { - var input = 32767; - - protocol.writeI16(input); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readI16(); + await primitiveTest(getPrimitive(TType.I16), 32767); + }); - expect(output, input); + test('Test I16 null', () async { + await primitiveNullTest(getPrimitive(TType.I16)); }); test('Test I32', () async { - var input = 2147483647; - - protocol.writeI32(input); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readI32(); + await primitiveTest(getPrimitive(TType.I32), 2147483647); + }); - expect(output, input); + test('Test I32 null', () async { + await primitiveNullTest(getPrimitive(TType.I32)); }); test('Test I64', () async { - var input = 9223372036854775807; - - protocol.writeI64(input); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readI64(); + await primitiveTest(getPrimitive(TType.I64), 9223372036854775807); + }); - expect(output, input); + test('Test I64 null', () async { + await primitiveNullTest(getPrimitive(TType.I64)); }); test('Test double', () async { - var input = 3.1415926; - - protocol.writeDouble(input); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readDouble(); + await primitiveTest(getPrimitive(TType.DOUBLE), 3.1415926); + }); - expect(output, input); + test('Test double null', () async { + await primitiveNullTest(getPrimitive(TType.DOUBLE)); }); test('Test string', () async { var input = 'There are only two hard things in computer science: ' 'cache invalidation, naming things, and off-by-one errors.'; + await primitiveTest(getPrimitive(TType.STRING), input); + }); - protocol.writeString(input); - protocol.writeMessageEnd(); - - await protocol.transport.flush(); - - protocol.readMessageBegin(); - var output = protocol.readString(); - - expect(output, input); + test('Test string null', () async { + await primitiveNullTest(getPrimitive(TType.STRING)); }); test('Test binary', () async { @@ -255,3 +267,12 @@ void main() { group('shared tests', sharedTests); }); } + + +class Primitive { + final Function read; + final Function write; + final defaultValue; + + Primitive(this.read, this.write, this.defaultValue); +} From 4c42de5651a2d6dfa8a6073f810dd14fa2935397 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Wed, 23 Sep 2015 00:16:07 -0500 Subject: [PATCH 48/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Added Dart support in build files. Integration tests working with Python Server on buffered transport. Fixed bug in generator with iterating over maps. Fixed typo in Python test server. Discovered bug in FramedTransport, which is not fixed yet. --- .gitignore | 12 + compiler/cpp/src/generate/t_dart_generator.cc | 15 +- configure.ac | 23 ++ lib/Makefile.am | 4 + lib/dart/.gitignore | 5 - lib/dart/Makefile.am | 26 ++ test/Makefile.am | 5 + test/dart/Makefile.am | 43 +++ test/dart/test_client/bin/main.dart | 290 ++++++++++++++++++ test/dart/test_client/pubspec.yaml | 15 + test/py/TestServer.py | 7 +- test/tests.json | 22 ++ tutorial/Makefile.am | 4 + tutorial/dart/.gitignore | 4 - tutorial/dart/Makefile.am | 56 ++++ tutorial/dart/console_client/pubspec.yaml | 3 +- tutorial/dart/server/pubspec.yaml | 2 +- 17 files changed, 513 insertions(+), 23 deletions(-) delete mode 100644 lib/dart/.gitignore create mode 100644 lib/dart/Makefile.am create mode 100755 test/dart/Makefile.am create mode 100644 test/dart/test_client/bin/main.dart create mode 100644 test/dart/test_client/pubspec.yaml delete mode 100644 tutorial/dart/.gitignore create mode 100644 tutorial/dart/Makefile.am diff --git a/.gitignore b/.gitignore index 4219468410c..38ca6d61771 100644 --- a/.gitignore +++ b/.gitignore @@ -145,6 +145,10 @@ test-driver /lib/d/test/serialization_benchmark /lib/d/test/transport_test /lib/d/unittest/ +/lib/dart/**/.packages +/lib/dart/**/packages +/lib/dart/**/.pub/ +/lib/dart/**/pubspec.lock /lib/delphi/src/*.dcu /lib/delphi/test/*.identcache /lib/delphi/test/*.local @@ -235,6 +239,10 @@ test-driver /test/cpp/StressTestNonBlocking /test/cpp/TestClient /test/cpp/TestServer +/test/dart/**/.packages +/test/dart/**/packages +/test/dart/**/.pub/ +/test/dart/**/pubspec.lock /test/log/ /test/test.log /test/erl/.eunit/ @@ -256,6 +264,10 @@ test-driver /tutorial/cpp/TutorialServer /tutorial/c_glib/tutorial_client /tutorial/c_glib/tutorial_server +/tutorial/dart/**/.packages +/tutorial/dart/**/packages +/tutorial/dart/**/.pub/ +/tutorial/dart/**/pubspec.lock /tutorial/delphi/*.dsk /tutorial/delphi/*.local /tutorial/delphi/*.tvsconfig diff --git a/compiler/cpp/src/generate/t_dart_generator.cc b/compiler/cpp/src/generate/t_dart_generator.cc index 33ab24cf814..4f4f3ef0bf3 100644 --- a/compiler/cpp/src/generate/t_dart_generator.cc +++ b/compiler/cpp/src/generate/t_dart_generator.cc @@ -1966,15 +1966,8 @@ void t_dart_generator::generate_serialize_container(ofstream& out, t_type* ttype if (ttype->is_map()) { string iter = tmp("_key"); - string counter = tmp("_sizeCounter"); - indent(out) << "int " << counter << " = 0;" << endl; - indent(out) << "for (var " << iter << " in " << prefix << ")"; - scope_up(out); - indent(out) << counter << +"++;" << endl; - scope_down(out); - indent(out) << "oprot.writeMapBegin(new TMap(" << type_to_enum(((t_map*)ttype)->get_key_type()) - << ", " << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " << counter << "));" + << ", " << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " << prefix << ".length));" << endl; } else if (ttype->is_set()) { indent(out) << "oprot.writeSetBegin(new TSet(" << type_to_enum(((t_set*)ttype)->get_elem_type()) @@ -1986,7 +1979,9 @@ void t_dart_generator::generate_serialize_container(ofstream& out, t_type* ttype } string iter = tmp("elem"); - if (ttype->is_map() || ttype->is_set() || ttype->is_list()) { + if (ttype->is_map()) { + indent(out) << "for (var " << iter << " in " << prefix << ".keys)"; + } else if (ttype->is_set() || ttype->is_list()) { indent(out) << "for (var " << iter << " in " << prefix << ")"; } @@ -2354,4 +2349,4 @@ THRIFT_REGISTER_GENERATOR( dart, "Dart", " library_name=my_library Optional override for library name.\n" -); +) diff --git a/configure.ac b/configure.ac index 880cc3225bb..e6b3be0e68f 100755 --- a/configure.ac +++ b/configure.ac @@ -122,6 +122,7 @@ if test "$enable_libs" = "no"; then with_perl="no" with_php="no" with_php_extension="no" + with_dart="no" with_erlang="no" with_go="no" with_d="no" @@ -325,6 +326,16 @@ AM_CONDITIONAL(WITH_PHP_EXTENSION, [test "$have_php_extension" = "yes"]) AC_PATH_PROG([PHPUNIT], [phpunit]) AM_CONDITIONAL(HAVE_PHPUNIT, [test "x$PHPUNIT" != "x"]) +AX_THRIFT_LIB(dart, [DART], yes) +if test "$with_dart" = "yes"; then + AC_PATH_PROG([DART], [dart]) + AC_PATH_PROG([DARTPUB], [pub]) + if test "x$DART" != "x" -a "x$DARTPUB" != "x"; then + have_dart="yes" + fi +fi +AM_CONDITIONAL(WITH_DART, [test "$have_dart" = "yes"]) + AX_THRIFT_LIB(ruby, [Ruby], yes) have_ruby=no if test "$with_ruby" = "yes"; then @@ -727,6 +738,7 @@ AC_CONFIG_FILES([ lib/perl/test/Makefile lib/php/Makefile lib/php/test/Makefile + lib/dart/Makefile lib/py/Makefile lib/rb/Makefile lib/lua/Makefile @@ -738,6 +750,7 @@ AC_CONFIG_FILES([ test/haxe/Makefile test/hs/Makefile test/php/Makefile + test/dart/Makefile test/perl/Makefile test/py/Makefile test/py.twisted/Makefile @@ -752,6 +765,7 @@ AC_CONFIG_FILES([ tutorial/java/Makefile tutorial/js/Makefile tutorial/nodejs/Makefile + tutorial/dart/Makefile tutorial/py/Makefile tutorial/py.twisted/Makefile tutorial/py.tornado/Makefile @@ -776,6 +790,8 @@ if test "$have_perl" = "yes" ; then MAYBE_PERL="perl" ; else MAYBE_PERL="" ; fi AC_SUBST([MAYBE_PERL]) if test "$have_php" = "yes" ; then MAYBE_PHP="php" ; else MAYBE_PHP="" ; fi AC_SUBST([MAYBE_PHP]) +if test "$have_dart" = "yes" ; then MAYBE_DART="dart" ; else MAYBE_DART="" ; fi +AC_SUBST([MAYBE_DART]) if test "$have_go" = "yes" ; then MAYBE_GO="go" ; else MAYBE_GO="" ; fi AC_SUBST([MAYBE_GO]) if test "$have_nodejs" = "yes" ; then MAYBE_NODEJS="nodejs" ; else MAYBE_NODEJS="" ; fi @@ -797,6 +813,7 @@ echo "Building Haxe Library ........ : $have_haxe" echo "Building Haskell Library ..... : $have_haskell" echo "Building Perl Library ........ : $have_perl" echo "Building PHP Library ......... : $have_php" +echo "Building Dart Library ........ : $have_dart" echo "Building Erlang Library ...... : $have_erlang" echo "Building Go Library .......... : $have_go" echo "Building D Library ........... : $have_d" @@ -834,6 +851,12 @@ if test "$have_php" = "yes" ; then echo "PHP Library:" echo " Using php-config .......... : $PHP_CONFIG" fi +if test "$have_dart" = "yes" ; then + echo + echo "Dart Library:" + echo " Using Dart ................ : $DART" + echo " Using Pub ................. : $DARTPUB" +fi if test "$have_ruby" = "yes" ; then echo echo "Ruby Library:" diff --git a/lib/Makefile.am b/lib/Makefile.am index 3f7ddc9a891..44bb9ff4121 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -65,6 +65,10 @@ if WITH_PHP SUBDIRS += php endif +if WITH_DART +SUBDIRS += dart +endif + if WITH_GO SUBDIRS += go endif diff --git a/lib/dart/.gitignore b/lib/dart/.gitignore deleted file mode 100644 index 87db33ec98b..00000000000 --- a/lib/dart/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.packages -packages -.pub/ -pubspec.lock -/coverage/ diff --git a/lib/dart/Makefile.am b/lib/dart/Makefile.am new file mode 100644 index 00000000000..6dfff40ba1c --- /dev/null +++ b/lib/dart/Makefile.am @@ -0,0 +1,26 @@ +# +# 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. +# + +all-local: + $(DARTPUB) get + +clean-local: + $(RM) -r .pub + +check-local: all diff --git a/test/Makefile.am b/test/Makefile.am index 6ebcd27f33c..7590921a58e 100755 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -40,6 +40,11 @@ SUBDIRS += php PRECROSS_TARGET += precross-php endif +if WITH_DART +SUBDIRS += dart +PRECROSS_TARGET += precross-dart +endif + if WITH_PYTHON SUBDIRS += py PRECROSS_TARGET += precross-py diff --git a/test/dart/Makefile.am b/test/dart/Makefile.am new file mode 100755 index 00000000000..4fd837fc3e4 --- /dev/null +++ b/test/dart/Makefile.am @@ -0,0 +1,43 @@ +# +# 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. +# + +THRIFT = $(top_builddir)/compiler/cpp/thrift + +gen-dart/thrift_test/lib/thrift_test.dart: ../ThriftTest.thrift + $(THRIFT) --gen dart ../ThriftTest.thrift + +stubs: gen-dart/thrift_test/lib/thrift_test.dart pub-get + +precross: stubs + +check: stubs + +clean-local: + $(RM) -r gen-dart + +pub-get: pub-get-gen pub-get-client + +pub-get-gen: gen-dart/thrift_test/lib/thrift_test.dart + cd gen-dart/thrift_test; ${DARTPUB} get + +pub-get-client: + cd test_client; ${DARTPUB} get + +client: stubs + ${DART} test_client/bin/main.dart diff --git a/test/dart/test_client/bin/main.dart b/test/dart/test_client/bin/main.dart new file mode 100644 index 00000000000..db98d241611 --- /dev/null +++ b/test/dart/test_client/bin/main.dart @@ -0,0 +1,290 @@ +/// 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. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:collection/equality.dart'; +import 'package:thrift/thrift.dart'; +import 'package:thrift/thrift_console.dart'; +import 'package:thrift_test/thrift_test.dart'; + +ThriftTestClient client; +bool verbose; + +/// Adapted from TestClient.php +main(List args) async { + var parser = new ArgParser(); + parser.addOption('host', defaultsTo: 'localhost', help: 'The server host'); + parser.addOption('port', defaultsTo: '9090', help: 'The port to connect to'); + parser.addOption('transport', + defaultsTo: 'buffered', + allowed: ['buffered', 'framed'], + help: 'The transport name', + allowedHelp: { + 'buffered': 'TBufferedTransport', + 'framed': 'TFramedTransport' + }); + parser.addOption('protocol', + defaultsTo: 'binary', + allowed: ['binary', 'json'], + help: 'The protocol name', + allowedHelp: {'binary': 'TBinaryProtocol', 'json': 'TJsonProtocol'}); + parser.addFlag('verbose', defaultsTo: false); + + ArgResults results; + try { + results = parser.parse(args); + } catch (e) { + stdout.writeln('$e\n'); + results = null; + } + + if (results == null) { + print(parser.usage); + exit(0); + } + + verbose = results['verbose'] == true; + + await init( + host: results['host'], + port: int.parse(results['port']), + transportType: results['transport'], + protocolType: results['protocol']).then((_) { + exit(0); + }).catchError((e) { + stdout.writeln('Error:'); + stdout.writeln('$e'); + if (e is Error) { + stdout.writeln('${e.stackTrace}'); + } + exit(1); + }); + exit(0); +} + +TProtocolFactory getProtocolFactory(String protocolType) { + if (protocolType == 'binary') { + return new TBinaryProtocolFactory(); + } else if (protocolType == 'json') { + return new TJsonProtocolFactory(); + } + + throw new ArgumentError.value(protocolType); +} + +Future init( + {String host, int port, String transportType, String protocolType}) async { + TTransport transport; + var protocolFactory = getProtocolFactory(protocolType); + + if (transportType == 'http') { + var httpClient = new HttpClient(); + var config = new THttpConfig(Uri.parse('http://localhost'), {}); + transport = new THttpClientTransport(httpClient, config); + } else { + var socket = await Socket.connect(host, port); + transport = + new TClientSocketTransport(new TTcpSocket(socket), protocolFactory); + if (transportType == 'framed') { + transport = new TFramedTransport(transport); + } + } + + var protocol = protocolFactory.getProtocol(transport); + client = new ThriftTestClient(protocol); + + await transport.open(); + + await runTests(); +} + +Future _test(String name, Function testFunc) async { + if (verbose) stdout.write('$name... '); + await testFunc(); + if (verbose) stdout.writeln('success!'); +} + +Future runTests() async { + var xtruct = new Xtruct() + ..string_thing = 'Zero' + ..byte_thing = 1 + ..i32_thing = -3 + ..i64_thing = -5; + + await _test('testVoid', () async { + await client.testVoid(); + }); + + await _test('testString', () async { + var input = 'Test'; + var result = await client.testString(input); + if (result != input) throw new StateError('$result != $input'); + }); + + await _test('testByte', () async { + var input = 64; + var result = await client.testByte(input); + if (result != input) throw new StateError('$result != $input'); + }); + + await _test('testI32', () async { + var input = 2147483647; + var result = await client.testI32(input); + if (result != input) throw new StateError('$result != $input'); + }); + + await _test('testI64', () async { + var input = 9223372036854775807; + var result = await client.testI64(input); + if (result != input) throw new StateError('$result != $input'); + }); + + await _test('testDouble', () async { + var input = 3.1415926; + var result = await client.testDouble(input); + if (result != input) throw new StateError('$result != $input'); + }); + + await _test('testBinary', () async { + var utf8Codec = const Utf8Codec(); + var input = utf8Codec.encode('foo'); + var result = await client.testBinary(input); + var equality = const ListEquality(); + if (!equality.equals( + result, input)) throw new StateError('$result != $input'); + }); + + await _test('testStruct', () async { + var result = await client.testStruct(xtruct); + if ('$result' != '$xtruct') throw new StateError('$result != $xtruct'); + }); + + await _test('testNest', () async { + var input = new Xtruct2() + ..byte_thing = 1 + ..struct_thing = xtruct + ..i32_thing = -3; + + var result = await client.testNest(input); + if ('$result' != '$input') throw new StateError('$result != $input'); + }); + + await _test('testMap', () async { + Map input = {1: -10, 2: -9, 3: -8, 4: -7, 5: -6}; + + var result = await client.testMap(input); + var equality = const MapEquality(); + if (!equality.equals( + result, input)) throw new StateError('$result != $input'); + }); + + await _test('testSet', () async { + var input = new Set.from([-2, -1, 0, 1, 2]); + var result = await client.testSet(input); + var equality = const SetEquality(); + if (!equality.equals( + result, input)) throw new StateError('$result != $input'); + }); + + await _test('testList', () async { + var input = [-2, -1, 0, 1, 2]; + var result = await client.testList(input); + var equality = const ListEquality(); + if (!equality.equals( + result, input)) throw new StateError('$result != $input'); + }); + + await _test('testEnum', () async { + await _testEnum(Numberz.ONE); + await _testEnum(Numberz.TWO); + await _testEnum(Numberz.THREE); + await _testEnum(Numberz.FIVE); + await _testEnum(Numberz.EIGHT); + }); + + await _test('testTypedef', () async { + var input = 309858235082523; + var result = await client.testTypedef(input); + if (result != input) throw new StateError('$result != $input'); + }); + + await _test('testMapMap', () async { + Map> result = await client.testMapMap(1); + if (result.isEmpty || + result[result.keys.first].isEmpty) throw new StateError( + 'expected Map> got $result'); + }); + + await _test('testMapMap', () async { + Map> result = await client.testMapMap(1); + if (result.isEmpty || + result[result.keys.first].isEmpty) throw new StateError( + 'expected Map> got $result'); + }); + + await _test('testInsanity', () async { + var input = new Insanity(); + input.userMap = {Numberz.FIVE: 5000}; + input.xtructs = [xtruct]; + + Map> result = await client.testInsanity(input); + if (result.isEmpty || + result[result.keys.first].isEmpty) throw new StateError( + 'expected Map> got $result'); + }); + + await _test('testMulti', () async { + var input = new Xtruct() + ..string_thing = 'Hello2' + ..byte_thing = 123 + ..i32_thing = 456 + ..i64_thing = 789; + + var result = await client.testMulti(input.byte_thing, input.i32_thing, + input.i64_thing, {1: 'one'}, Numberz.EIGHT, 5678); + if ('$result' != '$input') throw new StateError('$result != $input'); + }); + + await _test('testException', () async { + try { + await client.testException('Xception'); + } on Xception catch (x) { + return; + } + + throw new StateError('expected an Xception'); + }); + + await _test('testMultiException', () async { + try { + await client.testMultiException('Xception2', 'foo'); + } on Xception2 catch (x) { + return; + } + + throw new StateError('expected an Xception2'); + }); +} + +Future _testEnum(int input) async { + var result = await client.testEnum(input); + if (result != input) throw new StateError('$result != $input'); +} diff --git a/test/dart/test_client/pubspec.yaml b/test/dart/test_client/pubspec.yaml new file mode 100644 index 00000000000..599fec841ea --- /dev/null +++ b/test/dart/test_client/pubspec.yaml @@ -0,0 +1,15 @@ +name: thrift_test_client +version: 0.1.0 +description: A client integration test for the Dart Thrift library +author: Mark Erickson +homepage: https://github.com/apache/thrift +environment: + sdk: ^1.12.0 +dependencies: + args: ^0.13.0 + thrift: + path: ../../../lib/dart + thrift_test: + path: ../gen-dart/thrift_test +dev_dependencies: + test: "^0.12.0" diff --git a/test/py/TestServer.py b/test/py/TestServer.py index bcf9376de52..7d1c2e6e3dd 100755 --- a/test/py/TestServer.py +++ b/test/py/TestServer.py @@ -23,6 +23,9 @@ sys.path.insert(0, glob.glob(os.path.join(os.path.dirname(__file__),'../../lib/py/build/lib.*'))[0]) from optparse import OptionParser +import logging +logging.basicConfig() + parser = OptionParser() parser.add_option('--genpydir', type='string', dest='genpydir', default='gen-py', @@ -104,8 +107,8 @@ def testDouble(self, dub): def testBinary(self, thing): if options.verbose > 1: print 'testBinary()' # TODO: hex output - return thring - + return thing + def testStruct(self, thing): if options.verbose > 1: print 'testStruct({%s, %d, %d, %d})' % (thing.string_thing, thing.byte_thing, thing.i32_thing, thing.i64_thing) diff --git a/test/tests.json b/test/tests.json index d7caccbb2bc..db577c8ebd3 100644 --- a/test/tests.json +++ b/test/tests.json @@ -355,5 +355,27 @@ ] }, "workdir": "php" + }, + { + "name": "dart", + "client": { + "transports": [ + "buffered", + "framed", + "http" + ], + "sockets": [ + "ip" + ], + "protocols": [ + "binary", + "json" + ], + "command": [ + "dart", + "test_client/bin/main.dart" + ] + }, + "workdir": "dart" } ] diff --git a/tutorial/Makefile.am b/tutorial/Makefile.am index 47711a9cd10..37addda66f6 100755 --- a/tutorial/Makefile.am +++ b/tutorial/Makefile.am @@ -62,6 +62,10 @@ if WITH_NODEJS SUBDIRS += nodejs endif +if WITH_DART +SUBDIRS += dart +endif + # # generate html for ThriftTest.thrift # diff --git a/tutorial/dart/.gitignore b/tutorial/dart/.gitignore deleted file mode 100644 index acda527ef2a..00000000000 --- a/tutorial/dart/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -.packages -packages -.pub/ -pubspec.lock diff --git a/tutorial/dart/Makefile.am b/tutorial/dart/Makefile.am new file mode 100644 index 00000000000..2bb6bf2c7b3 --- /dev/null +++ b/tutorial/dart/Makefile.am @@ -0,0 +1,56 @@ +# +# 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. +# + +THRIFT = $(top_builddir)/compiler/cpp/thrift + +gen-dart/tutorial/lib/tutorial.dart gen-dart/shared/lib/shared.dart: $(top_srcdir)/tutorial/tutorial.thrift + $(THRIFT) --gen dart -r $< + +all-local: gen-dart/tutorial/lib/tutorial.dart pub-get + +clean-local: + $(RM) -r gen-* + +pub-get: pub-get-gen pub-get-client pub-get-console-client pub-get-server + +pub-get-gen: pub-get-tutorial pub-get-shared + +pub-get-tutorial: gen-dart/tutorial/lib/tutorial.dart + cd gen-dart/tutorial; ${DARTPUB} get + +pub-get-shared: gen-dart/shared/lib/shared.dart + cd gen-dart/shared; ${DARTPUB} get + +pub-get-client: + cd client; ${DARTPUB} get + +pub-get-console-client: + cd console_client; ${DARTPUB} get + +pub-get-server: + cd client; ${DARTPUB} get + +tutorialserver: pub-get-gen pub-get-server + ${DART} server/bin/main.dart + +tutorialclient: pub-get-gen pub-get-client + cd client; ${DARTPUB} serve + +tutorialconsoleclient: pub-get-console-client + ${DART} console-client/bin/main.dart diff --git a/tutorial/dart/console_client/pubspec.yaml b/tutorial/dart/console_client/pubspec.yaml index a11121ab66e..85656df6af9 100644 --- a/tutorial/dart/console_client/pubspec.yaml +++ b/tutorial/dart/console_client/pubspec.yaml @@ -1,5 +1,5 @@ name: tutorial_console_client -version: 0.0.1 +version: 0.1.0 description: > A Dart console client to implementation of the Apache Thrift tutorial author: Mark Erickson @@ -10,6 +10,7 @@ environment: dependencies: args: ^0.13.0 + collection: ^1.1.0 shared: path: ../gen-dart/shared thrift: diff --git a/tutorial/dart/server/pubspec.yaml b/tutorial/dart/server/pubspec.yaml index 744f09a9a4e..9e7558e070b 100644 --- a/tutorial/dart/server/pubspec.yaml +++ b/tutorial/dart/server/pubspec.yaml @@ -1,5 +1,5 @@ name: tutorial_server -version: 0.0.1 +version: 0.1.0 description: A Dart server to support the Apache Thrift tutorial author: Mark Erickson homepage: https://github.com/apache/thrift From de2cb695bdc8c8debcdee564b38aa5de62d76ccd Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Wed, 23 Sep 2015 12:57:46 -0500 Subject: [PATCH 49/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Make TClientSocketTransport simple and add TAsyncClientSocketTransport. Fixed issues with TMessageReader and TFramedTransport. --- .../lib/src/transport/t_framed_transport.dart | 8 +- .../lib/src/transport/t_message_reader.dart | 21 +++- .../lib/src/transport/t_socket_transport.dart | 45 +++++++- .../transport/t_socket_transport_test.dart | 107 +++++++++++++++--- test/dart/test_client/bin/main.dart | 3 +- tutorial/dart/client/web/client.dart | 5 +- tutorial/dart/console_client/bin/main.dart | 5 +- 7 files changed, 162 insertions(+), 32 deletions(-) diff --git a/lib/dart/lib/src/transport/t_framed_transport.dart b/lib/dart/lib/src/transport/t_framed_transport.dart index c2a57a4a733..baf8f321269 100644 --- a/lib/dart/lib/src/transport/t_framed_transport.dart +++ b/lib/dart/lib/src/transport/t_framed_transport.dart @@ -21,11 +21,11 @@ part of thrift; /// /// Adapted from the Java Framed transport. class TFramedTransport extends TBufferedTransport { - static const uint32ByteCount = 4; + static const int headerByteCount = 4; final TTransport _transport; - final Uint8List headerBytes = new Uint8List(uint32ByteCount); + final Uint8List headerBytes = new Uint8List(headerByteCount); TFramedTransport(TTransport transport) : _transport = transport { if (transport == null) { @@ -57,7 +57,7 @@ class TFramedTransport extends TBufferedTransport { } void _readFrame() { - _transport.readAll(headerBytes, 0, uint32ByteCount); + _transport.readAll(headerBytes, 0, headerByteCount); int size = headerBytes.buffer.asByteData().getUint32(0); if (size < 0) { @@ -75,7 +75,7 @@ class TFramedTransport extends TBufferedTransport { int length = buffer.length; headerBytes.buffer.asByteData().setUint32(0, length); - _transport.write(headerBytes, 0, uint32ByteCount); + _transport.write(headerBytes, 0, headerByteCount); _transport.write(buffer, 0, length); return _transport.flush(); diff --git a/lib/dart/lib/src/transport/t_message_reader.dart b/lib/dart/lib/src/transport/t_message_reader.dart index 7290e873488..c7c47d0a84b 100644 --- a/lib/dart/lib/src/transport/t_message_reader.dart +++ b/lib/dart/lib/src/transport/t_message_reader.dart @@ -22,13 +22,17 @@ part of thrift; class TMessageReader { final TProtocolFactory protocolFactory; + final int byteOffset; final _TMessageReaderTransport _transport; - TMessageReader(this.protocolFactory) - : _transport = new _TMessageReaderTransport(); + /// Construct a [MessageReader]. The [readOffset] specifies the number of + /// bytes to skip before reading the [TMessage]. + TMessageReader(this.protocolFactory, {int byteOffset: 0}) + : _transport = new _TMessageReaderTransport(), + this.byteOffset = byteOffset; TMessage readMessage(Uint8List bytes) { - _transport.reset(bytes); + _transport.reset(bytes, byteOffset); TProtocol protocol = protocolFactory.getProtocol(_transport); TMessage message = protocol.readMessageBegin(); _transport.reset(null); @@ -43,8 +47,15 @@ class _TMessageReaderTransport extends TTransport { Iterator _readIterator; - void reset(Uint8List bytes) { - _readIterator = bytes != null ? bytes.iterator : null; + void reset(Uint8List bytes, [int offset = 0]) { + if (bytes == null) { + _readIterator = null; + } else { + _readIterator = bytes.iterator; + for (var i = 0; i < offset; i++) { + _readIterator.moveNext(); + } + } } get isOpen => true; diff --git a/lib/dart/lib/src/transport/t_socket_transport.dart b/lib/dart/lib/src/transport/t_socket_transport.dart index c7939fb20df..ad7e48ef295 100644 --- a/lib/dart/lib/src/transport/t_socket_transport.dart +++ b/lib/dart/lib/src/transport/t_socket_transport.dart @@ -60,8 +60,47 @@ abstract class TSocketTransport extends TBufferedTransport { } } -/// [TClientSocketTransport] sends outgoing messages and expects a response +/// [TClientSocketTransport] is a basic client socket transport. It sends +/// outgoing messages and expects a response. +/// +/// NOTE: This transport expects a single threaded server, as it will process +/// responses in FIFO order. class TClientSocketTransport extends TSocketTransport { + final List> _completers = []; + + TClientSocketTransport(TSocket socket) : super(socket); + + Future flush() { + Uint8List bytes = _consumeWriteBuffer(); + + // Use a sync completer to ensure that the buffer can be read immediately + // after the read buffer is set, and avoid a race condition where another + // response could overwrite the read buffer. + Completer completer = new Completer.sync(); + _completers.add(completer); + + socket.send(bytes); + + return completer.future; + } + + void handleIncomingMessage(Uint8List messageBytes) { + super.handleIncomingMessage(messageBytes); + + if (_completers.isNotEmpty) { + var completer = _completers.removeAt(0); + completer.complete(); + } + } +} + +/// [TAsyncClientSocketTransport] sends outgoing messages and expects an +/// asynchronous response. +/// +/// NOTE: This transport uses a [MessageReader] to read a [TMessage] when an +/// incoming message arrives to correlate a response to a request, using the +/// seqid. +class TAsyncClientSocketTransport extends TSocketTransport { static const defaultTimeout = const Duration(seconds: 30); final Map> _completers = {}; @@ -70,9 +109,9 @@ class TClientSocketTransport extends TSocketTransport { final Duration responseTimeout; - TClientSocketTransport(TSocket socket, TProtocolFactory protocolFactory, + TAsyncClientSocketTransport(TSocket socket, TMessageReader messageReader, {Duration responseTimeout: defaultTimeout}) - : messageReader = new TMessageReader(protocolFactory), + : this.messageReader = messageReader, this.responseTimeout = responseTimeout, super(socket); diff --git a/lib/dart/test/transport/t_socket_transport_test.dart b/lib/dart/test/transport/t_socket_transport_test.dart index caa4a3c34a4..b1bf5756cf8 100644 --- a/lib/dart/test/transport/t_socket_transport_test.dart +++ b/lib/dart/test/transport/t_socket_transport_test.dart @@ -37,19 +37,18 @@ void main() { final responseBytes = new Uint8List.fromList(utf8Codec.encode(responseText)); final responseBase64 = CryptoUtils.bytesToBase64(responseBytes); - group('TClientTransport', () { + final framedResponseBase64 = + CryptoUtils.bytesToBase64(_getFramedResponse(responseBytes)); + + group('TClientSocketTransport', () { FakeSocket socket; - FakeProtocolFactory protocolFactory; TTransport transport; setUp(() async { socket = new FakeSocket(sync: false); await socket.open(); - - protocolFactory = new FakeProtocolFactory(); - protocolFactory.message = new TMessage('foo', TMessageType.CALL, 123); - transport = new TClientSocketTransport(socket, protocolFactory, - responseTimeout: Duration.ZERO); + transport = new TClientSocketTransport(socket); + await transport.open(); transport.writeAll(requestBytes); }); @@ -65,7 +64,6 @@ void main() { expect(socket.sendPayload, requestBytes); // simulate a response - protocolFactory.message = new TMessage('foo', TMessageType.REPLY, 123); socket.receiveFakeMessage(responseBase64); await responseReady; @@ -75,14 +73,39 @@ void main() { expect(bufferText, responseText); }); + }, timeout: new Timeout(new Duration(seconds: 1))); - test('Test response timeout', () async { - Future responseReady = transport.flush(); - expect(responseReady, throwsA(new isInstanceOf())); + group('TClientSocketTransport with FramedTransport', () { + FakeSocket socket; + TTransport transport; + + setUp(() async { + socket = new FakeSocket(sync: true); + await socket.open(); + + transport = new TFramedTransport(new TClientSocketTransport(socket)); + await transport.open(); + transport.writeAll(requestBytes); + }); + + test('Test client sending data over framed transport', () async { + String bufferText; + + Future responseReady = transport.flush().then((_) { + var buffer = new Uint8List(responseBytes.length); + transport.readAll(buffer, 0, responseBytes.length); + bufferText = utf8Codec.decode(buffer); + }); + + // simulate a response + socket.receiveFakeMessage(framedResponseBase64); + + await responseReady; + expect(bufferText, responseText); }); }, timeout: new Timeout(new Duration(seconds: 1))); - group('TClientTransport with multiple messages', () { + group('TAsyncClientSocketTransport', () { FakeSocket socket; FakeProtocolFactory protocolFactory; TTransport transport; @@ -93,12 +116,14 @@ void main() { protocolFactory = new FakeProtocolFactory(); protocolFactory.message = new TMessage('foo', TMessageType.CALL, 123); - transport = new TClientSocketTransport(socket, protocolFactory, + transport = new TAsyncClientSocketTransport(socket, + new TMessageReader(protocolFactory), responseTimeout: Duration.ZERO); + await transport.open(); transport.writeAll(requestBytes); }); - test('Test read correct buffer after flush', () async { + test('Test response correlates to correct request', () async { String bufferText; Future responseReady = transport.flush().then((_) { @@ -122,6 +147,50 @@ void main() { await responseReady; expect(bufferText, responseText); }); + + test('Test response timeout', () async { + Future responseReady = transport.flush(); + expect(responseReady, throwsA(new isInstanceOf())); + }); + }, timeout: new Timeout(new Duration(seconds: 1))); + + group('TAsyncClientSocketTransport with TFramedTransport', () { + FakeSocket socket; + FakeProtocolFactory protocolFactory; + TTransport transport; + + setUp(() async { + socket = new FakeSocket(sync: true); + await socket.open(); + + protocolFactory = new FakeProtocolFactory(); + protocolFactory.message = new TMessage('foo', TMessageType.CALL, 123); + var messageReader = new TMessageReader(protocolFactory, + byteOffset: TFramedTransport.headerByteCount); + + transport = new TFramedTransport(new TAsyncClientSocketTransport(socket, + messageReader, + responseTimeout: Duration.ZERO)); + await transport.open(); + transport.writeAll(requestBytes); + }); + + test('Test async client sending data over framed transport', () async { + String bufferText; + + Future responseReady = transport.flush().then((_) { + var buffer = new Uint8List(responseBytes.length); + transport.readAll(buffer, 0, responseBytes.length); + bufferText = utf8Codec.decode(buffer); + }); + + // simulate a response + protocolFactory.message = new TMessage('foo', TMessageType.REPLY, 123); + socket.receiveFakeMessage(framedResponseBase64); + + await responseReady; + expect(bufferText, responseText); + }); }, timeout: new Timeout(new Duration(seconds: 1))); group('TServerTransport', () { @@ -231,3 +300,13 @@ class FakeProtocol extends Mock implements TProtocol { readMessageBegin() => _message; } + +Uint8List _getFramedResponse(Uint8List responseBytes) { + var byteOffset = TFramedTransport.headerByteCount; + var response = new Uint8List(byteOffset + responseBytes.length); + + response.buffer.asByteData().setInt32(0, responseBytes.length); + response.setAll(byteOffset, responseBytes); + + return response; +} diff --git a/test/dart/test_client/bin/main.dart b/test/dart/test_client/bin/main.dart index db98d241611..d88c0251136 100644 --- a/test/dart/test_client/bin/main.dart +++ b/test/dart/test_client/bin/main.dart @@ -101,8 +101,7 @@ Future init( transport = new THttpClientTransport(httpClient, config); } else { var socket = await Socket.connect(host, port); - transport = - new TClientSocketTransport(new TTcpSocket(socket), protocolFactory); + transport = new TClientSocketTransport(new TTcpSocket(socket)); if (transportType == 'framed') { transport = new TFramedTransport(transport); } diff --git a/tutorial/dart/client/web/client.dart b/tutorial/dart/client/web/client.dart index fb64a42c526..4f02d0d9816 100644 --- a/tutorial/dart/client/web/client.dart +++ b/tutorial/dart/client/web/client.dart @@ -47,8 +47,9 @@ class CalculatorUI { } void _initConnection() { - _transport = new TClientSocketTransport( - new TWebSocket(Uri.parse('ws://127.0.0.1:9090/ws')), new TBinaryProtocolFactory()); + _transport = new TAsyncClientSocketTransport( + new TWebSocket(Uri.parse('ws://127.0.0.1:9090/ws')), + new TMessageReader(new TBinaryProtocolFactory())); TProtocol protocol = new TBinaryProtocol(_transport); _transport.open(); diff --git a/tutorial/dart/console_client/bin/main.dart b/tutorial/dart/console_client/bin/main.dart index 7cd4d8a9c00..a7ede171424 100644 --- a/tutorial/dart/console_client/bin/main.dart +++ b/tutorial/dart/console_client/bin/main.dart @@ -63,8 +63,9 @@ main(List args) { Future _initConnection(int port) async { var socket = await Socket.connect('127.0.0.1', port); - _transport = new TClientSocketTransport( - new TTcpSocket(socket), new TBinaryProtocolFactory()); + _transport = new TAsyncClientSocketTransport( + new TTcpSocket(socket), + new TMessageReader(new TBinaryProtocolFactory())); TProtocol protocol = new TBinaryProtocol(_transport); await _transport.open(); From c89d1d1fbbbe6747285ef22e7b0faf6623c74d1c Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Wed, 23 Sep 2015 20:56:09 -0500 Subject: [PATCH 50/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Fixed "off by one" issue with context stack in JSON protocol. --- .../lib/src/protocol/t_json_protocol.dart | 13 ++- lib/dart/test/protocol/t_protocol_test.dart | 100 +++++++++++++++++- .../transport/t_socket_transport_test.dart | 8 +- test/dart/test_client/bin/main.dart | 13 ++- tutorial/dart/console_client/bin/main.dart | 3 +- 5 files changed, 120 insertions(+), 17 deletions(-) diff --git a/lib/dart/lib/src/protocol/t_json_protocol.dart b/lib/dart/lib/src/protocol/t_json_protocol.dart index 805e937d511..4fa64999e16 100644 --- a/lib/dart/lib/src/protocol/t_json_protocol.dart +++ b/lib/dart/lib/src/protocol/t_json_protocol.dart @@ -32,12 +32,14 @@ class TJsonProtocol extends TProtocol { static const Utf8Codec utf8Codec = const Utf8Codec(); _BaseContext _context; + _BaseContext _rootContext; _LookaheadReader _reader; final List<_BaseContext> _contextStack = []; final Uint8List _tempBuffer = new Uint8List(4); TJsonProtocol(TTransport transport) : super(transport) { + _rootContext = new _BaseContext(this); _reader = new _LookaheadReader(this); } @@ -47,12 +49,13 @@ class TJsonProtocol extends TProtocol { } void _popContext() { - _context = _contextStack.removeLast(); + _contextStack.removeLast(); + _context = _contextStack.isEmpty ? _rootContext : _contextStack.last; } void _resetContext() { _contextStack.clear(); - _context = new _BaseContext(this); + _context = _rootContext; } /// Read a byte that must match [char]; otherwise throw a [TProtocolError]. @@ -688,6 +691,8 @@ class _BaseContext { void read() {} bool get escapeNumbers => false; + + String toString() => 'BaseContext'; } class _ListContext extends _BaseContext { @@ -710,6 +715,8 @@ class _ListContext extends _BaseContext { protocol._readJsonSyntaxChar(_Constants.COMMA_BYTES[0]); } } + + String toString() => 'ListContext'; } class _PairContext extends _BaseContext { @@ -742,4 +749,6 @@ class _PairContext extends _BaseContext { } bool get escapeNumbers => _colon; + + String toString() => 'PairContext'; } diff --git a/lib/dart/test/protocol/t_protocol_test.dart b/lib/dart/test/protocol/t_protocol_test.dart index 3b65e0e5dd4..88ddd4f105e 100644 --- a/lib/dart/test/protocol/t_protocol_test.dart +++ b/lib/dart/test/protocol/t_protocol_test.dart @@ -216,7 +216,7 @@ void main() { }); test('Test double', () async { - await primitiveTest(getPrimitive(TType.DOUBLE), 3.1415926); + await primitiveTest(getPrimitive(TType.DOUBLE), 3.1415926); }); test('Test double null', () async { @@ -247,6 +247,103 @@ void main() { expect(output.length, input.length); expect(output.every((i) => i == 123), isTrue); }); + + test('Test complex struct', () async { + // {1: {10: 20}, 2: {30: 40}} + protocol.writeStructBegin(new TStruct()); + protocol.writeFieldBegin(new TField('success', TType.MAP, 0)); + protocol.writeMapBegin(new TMap(TType.I32, TType.MAP, 2)); + + protocol.writeI32(1); // key + protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1)); + protocol.writeI32(10); // key + protocol.writeI32(20); // value + protocol.writeMapEnd(); + + protocol.writeI32(2); // key + protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1)); + protocol.writeI32(30); // key + protocol.writeI32(40); // value + protocol.writeMapEnd(); + + protocol.writeMapEnd(); + protocol.writeFieldEnd(); + protocol.writeFieldStop(); + protocol.writeStructEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + protocol.readStructBegin(); + expect(protocol.readFieldBegin().type, TType.MAP); + expect(protocol.readMapBegin().length, 2); + + expect(protocol.readI32(), 1); // key + expect(protocol.readMapBegin().length, 1); + expect(protocol.readI32(), 10); // key + expect(protocol.readI32(), 20); // value + protocol.readMapEnd(); + + expect(protocol.readI32(), 2); // key + expect(protocol.readMapBegin().length, 1); + expect(protocol.readI32(), 30); // key + expect(protocol.readI32(), 40); // value + protocol.readMapEnd(); + + protocol.readMapEnd(); + protocol.readFieldEnd(); + protocol.readStructEnd(); + protocol.readMessageEnd(); + }); + + test('Test nested maps and lists', () async { + // {1: [{10: 20}], 2: [{30: 40}]} + protocol.writeMapBegin(new TMap(TType.I32, TType.LIST, 2)); + + protocol.writeI32(1); // key + protocol.writeListBegin(new TList(TType.MAP, 1)); + protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1)); + protocol.writeI32(10); // key + protocol.writeI32(20); // value + protocol.writeMapEnd(); + protocol.writeListEnd(); + + protocol.writeI32(2); // key + protocol.writeListBegin(new TList(TType.MAP, 1)); + protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1)); + protocol.writeI32(30); // key + protocol.writeI32(40); // value + protocol.writeMapEnd(); + protocol.writeListEnd(); + + protocol.writeMapEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + expect(protocol.readMapBegin().length, 2); + + expect(protocol.readI32(), 1); // key + expect(protocol.readListBegin().length, 1); + expect(protocol.readMapBegin().length, 1); + expect(protocol.readI32(), 10); // key + expect(protocol.readI32(), 20); // value + protocol.readMapEnd(); + protocol.readListEnd(); + + expect(protocol.readI32(), 2); // key + expect(protocol.readListBegin().length, 1); + expect(protocol.readMapBegin().length, 1); + expect(protocol.readI32(), 30); // key + expect(protocol.readI32(), 40); // value + protocol.readMapEnd(); + protocol.readListEnd(); + + protocol.readMapEnd(); + protocol.readMessageEnd(); + }); }; group('JSON', () { @@ -268,7 +365,6 @@ void main() { }); } - class Primitive { final Function read; final Function write; diff --git a/lib/dart/test/transport/t_socket_transport_test.dart b/lib/dart/test/transport/t_socket_transport_test.dart index b1bf5756cf8..997df0db24f 100644 --- a/lib/dart/test/transport/t_socket_transport_test.dart +++ b/lib/dart/test/transport/t_socket_transport_test.dart @@ -116,8 +116,8 @@ void main() { protocolFactory = new FakeProtocolFactory(); protocolFactory.message = new TMessage('foo', TMessageType.CALL, 123); - transport = new TAsyncClientSocketTransport(socket, - new TMessageReader(protocolFactory), + transport = new TAsyncClientSocketTransport( + socket, new TMessageReader(protocolFactory), responseTimeout: Duration.ZERO); await transport.open(); transport.writeAll(requestBytes); @@ -168,8 +168,8 @@ void main() { var messageReader = new TMessageReader(protocolFactory, byteOffset: TFramedTransport.headerByteCount); - transport = new TFramedTransport(new TAsyncClientSocketTransport(socket, - messageReader, + transport = new TFramedTransport(new TAsyncClientSocketTransport( + socket, messageReader, responseTimeout: Duration.ZERO)); await transport.open(); transport.writeAll(requestBytes); diff --git a/test/dart/test_client/bin/main.dart b/test/dart/test_client/bin/main.dart index d88c0251136..3733a085f7f 100644 --- a/test/dart/test_client/bin/main.dart +++ b/test/dart/test_client/bin/main.dart @@ -138,6 +138,12 @@ Future runTests() async { if (result != input) throw new StateError('$result != $input'); }); + await _test('testBool', () async { + var input = true; + var result = await client.testBool(input); + if (result != input) throw new StateError('$result != $input'); + }); + await _test('testByte', () async { var input = 64; var result = await client.testByte(input); @@ -232,13 +238,6 @@ Future runTests() async { 'expected Map> got $result'); }); - await _test('testMapMap', () async { - Map> result = await client.testMapMap(1); - if (result.isEmpty || - result[result.keys.first].isEmpty) throw new StateError( - 'expected Map> got $result'); - }); - await _test('testInsanity', () async { var input = new Insanity(); input.userMap = {Numberz.FIVE: 5000}; diff --git a/tutorial/dart/console_client/bin/main.dart b/tutorial/dart/console_client/bin/main.dart index a7ede171424..fda206ab9b5 100644 --- a/tutorial/dart/console_client/bin/main.dart +++ b/tutorial/dart/console_client/bin/main.dart @@ -64,8 +64,7 @@ main(List args) { Future _initConnection(int port) async { var socket = await Socket.connect('127.0.0.1', port); _transport = new TAsyncClientSocketTransport( - new TTcpSocket(socket), - new TMessageReader(new TBinaryProtocolFactory())); + new TTcpSocket(socket), new TMessageReader(new TBinaryProtocolFactory())); TProtocol protocol = new TBinaryProtocol(_transport); await _transport.open(); From c19eed025c40939d9e6b00aca307bf571bbca483 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Wed, 23 Sep 2015 21:47:53 -0500 Subject: [PATCH 51/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Address code review comments to add missing License text and update version to match Thrift version. --- compiler/cpp/src/generate/t_dart_generator.cc | 4 ++-- lib/dart/pubspec.yaml | 19 ++++++++++++++++- test/dart/test_client/pubspec.yaml | 19 ++++++++++++++++- tutorial/dart/build.sh | 17 +++++++++++++++ tutorial/dart/client/web/index.html | 18 ++++++++++++++++ tutorial/dart/client/web/styles.css | 18 ++++++++++++++++ tutorial/dart/console_client/pubspec.yaml | 21 ++++++++++++++++--- tutorial/dart/server/pubspec.yaml | 21 ++++++++++++++++--- 8 files changed, 127 insertions(+), 10 deletions(-) diff --git a/compiler/cpp/src/generate/t_dart_generator.cc b/compiler/cpp/src/generate/t_dart_generator.cc index 4f4f3ef0bf3..c7bf81286df 100644 --- a/compiler/cpp/src/generate/t_dart_generator.cc +++ b/compiler/cpp/src/generate/t_dart_generator.cc @@ -41,7 +41,7 @@ static const string endl = "\n"; // avoid ostream << std::endl flushes static const string endl2 = "\n\n"; /* Should reflect the current version in lib/dart/pubspec.yaml */ -static const string dart_thrift_version = "0.1.0"; +static const string dart_thrift_version = "0.9.3"; /* forward declarations */ string initial_caps_to_underscores(string name); @@ -358,7 +358,7 @@ void t_dart_generator::generate_dart_pubspec() { indent(f_pubspec) << "dependencies:" << endl; indent_up(); - indent(f_pubspec) << "thrift: # '>=" << dart_thrift_version << "'" << endl; + indent(f_pubspec) << "thrift: # ^" << dart_thrift_version << endl; indent_up(); indent(f_pubspec) << "path: ../../../../lib/dart" << endl; indent_down(); diff --git a/lib/dart/pubspec.yaml b/lib/dart/pubspec.yaml index 4df26f2666b..c8bee01d3c9 100644 --- a/lib/dart/pubspec.yaml +++ b/lib/dart/pubspec.yaml @@ -1,5 +1,22 @@ +# 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. + name: thrift -version: 0.1.0 +version: 0.9.3 description: > A Dart library for Apache Thrift author: Mark Erickson diff --git a/test/dart/test_client/pubspec.yaml b/test/dart/test_client/pubspec.yaml index 599fec841ea..d0134775a18 100644 --- a/test/dart/test_client/pubspec.yaml +++ b/test/dart/test_client/pubspec.yaml @@ -1,5 +1,22 @@ +# 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. + name: thrift_test_client -version: 0.1.0 +version: 0.9.3 description: A client integration test for the Dart Thrift library author: Mark Erickson homepage: https://github.com/apache/thrift diff --git a/tutorial/dart/build.sh b/tutorial/dart/build.sh index 198b9f0cd57..eabe04a18af 100755 --- a/tutorial/dart/build.sh +++ b/tutorial/dart/build.sh @@ -1,5 +1,22 @@ #!/bin/sh +# 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. + set -e; rm -r gen-dart || true; diff --git a/tutorial/dart/client/web/index.html b/tutorial/dart/client/web/index.html index 6d62f58f078..64b184e5561 100644 --- a/tutorial/dart/client/web/index.html +++ b/tutorial/dart/client/web/index.html @@ -1,3 +1,21 @@ + diff --git a/tutorial/dart/client/web/styles.css b/tutorial/dart/client/web/styles.css index 6b3683804c7..c0315025b8d 100644 --- a/tutorial/dart/client/web/styles.css +++ b/tutorial/dart/client/web/styles.css @@ -1,3 +1,21 @@ +/* + * 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. + */ @import url(https://fonts.googleapis.com/css?family=Roboto); html, body { diff --git a/tutorial/dart/console_client/pubspec.yaml b/tutorial/dart/console_client/pubspec.yaml index 85656df6af9..32fe52e1299 100644 --- a/tutorial/dart/console_client/pubspec.yaml +++ b/tutorial/dart/console_client/pubspec.yaml @@ -1,13 +1,28 @@ +# 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. + name: tutorial_console_client -version: 0.1.0 +version: 0.9.3 description: > A Dart console client to implementation of the Apache Thrift tutorial author: Mark Erickson homepage: https://github.com/apache/thrift - environment: sdk: ^1.12.0 - dependencies: args: ^0.13.0 collection: ^1.1.0 diff --git a/tutorial/dart/server/pubspec.yaml b/tutorial/dart/server/pubspec.yaml index 9e7558e070b..74f17b7c5b6 100644 --- a/tutorial/dart/server/pubspec.yaml +++ b/tutorial/dart/server/pubspec.yaml @@ -1,12 +1,27 @@ +# 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. + name: tutorial_server -version: 0.1.0 +version: 0.9.3 description: A Dart server to support the Apache Thrift tutorial author: Mark Erickson homepage: https://github.com/apache/thrift - environment: sdk: ^1.12.0 - dependencies: args: ^0.13.0 shared: From f46b1a62110012ca088335541dea0ff382da21e6 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Thu, 24 Sep 2015 07:45:17 -0500 Subject: [PATCH 52/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Updated version to 1.0.0-dev to match current Thrift version. Added missing license. --- compiler/cpp/src/generate/t_dart_generator.cc | 13 +++++++++++-- lib/dart/pubspec.yaml | 2 +- test/dart/test_client/pubspec.yaml | 2 +- tutorial/dart/client/pubspec.yaml | 19 ++++++++++++++++++- tutorial/dart/console_client/pubspec.yaml | 2 +- tutorial/dart/server/pubspec.yaml | 2 +- 6 files changed, 33 insertions(+), 7 deletions(-) diff --git a/compiler/cpp/src/generate/t_dart_generator.cc b/compiler/cpp/src/generate/t_dart_generator.cc index c7bf81286df..147ca138b7d 100644 --- a/compiler/cpp/src/generate/t_dart_generator.cc +++ b/compiler/cpp/src/generate/t_dart_generator.cc @@ -40,8 +40,17 @@ using std::vector; static const string endl = "\n"; // avoid ostream << std::endl flushes static const string endl2 = "\n\n"; -/* Should reflect the current version in lib/dart/pubspec.yaml */ -static const string dart_thrift_version = "0.9.3"; +/** + * Use the current Thrift version for static libraries. When releasing, update + * the version in these files. + * - lib/dart/pubspec.yaml + * - test/dart/test_client/pubspec.yaml + * - tutorial/dart/client/pubspec.yaml + * - tutorial/dart/console_client/pubspec.yaml + * - tutorial/dart/server/pubspec.yaml + * See https://thrift.apache.org/docs/committers/HowToVersion + */ +static const string dart_thrift_version = THRIFT_VERSION; /* forward declarations */ string initial_caps_to_underscores(string name); diff --git a/lib/dart/pubspec.yaml b/lib/dart/pubspec.yaml index c8bee01d3c9..f64d9806d58 100644 --- a/lib/dart/pubspec.yaml +++ b/lib/dart/pubspec.yaml @@ -16,7 +16,7 @@ # under the License. name: thrift -version: 0.9.3 +version: 1.0.0-dev description: > A Dart library for Apache Thrift author: Mark Erickson diff --git a/test/dart/test_client/pubspec.yaml b/test/dart/test_client/pubspec.yaml index d0134775a18..54d8b0fccaf 100644 --- a/test/dart/test_client/pubspec.yaml +++ b/test/dart/test_client/pubspec.yaml @@ -16,7 +16,7 @@ # under the License. name: thrift_test_client -version: 0.9.3 +version: 1.0.0-dev description: A client integration test for the Dart Thrift library author: Mark Erickson homepage: https://github.com/apache/thrift diff --git a/tutorial/dart/client/pubspec.yaml b/tutorial/dart/client/pubspec.yaml index c0cde1eec85..97c625beb11 100644 --- a/tutorial/dart/client/pubspec.yaml +++ b/tutorial/dart/client/pubspec.yaml @@ -1,5 +1,22 @@ +# 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. + name: tutorial_client -version: 0.1.0 +version: 1.0.0-dev description: A Dart client implementation of the Apache Thrift tutorial author: Mark Erickson homepage: https://github.com/apache/thrift diff --git a/tutorial/dart/console_client/pubspec.yaml b/tutorial/dart/console_client/pubspec.yaml index 32fe52e1299..3db9fb9d011 100644 --- a/tutorial/dart/console_client/pubspec.yaml +++ b/tutorial/dart/console_client/pubspec.yaml @@ -16,7 +16,7 @@ # under the License. name: tutorial_console_client -version: 0.9.3 +version: 1.0.0-dev description: > A Dart console client to implementation of the Apache Thrift tutorial author: Mark Erickson diff --git a/tutorial/dart/server/pubspec.yaml b/tutorial/dart/server/pubspec.yaml index 74f17b7c5b6..f502974e7c5 100644 --- a/tutorial/dart/server/pubspec.yaml +++ b/tutorial/dart/server/pubspec.yaml @@ -16,7 +16,7 @@ # under the License. name: tutorial_server -version: 0.9.3 +version: 1.0.0-dev description: A Dart server to support the Apache Thrift tutorial author: Mark Erickson homepage: https://github.com/apache/thrift From 137ef759a290b90daf6feab22110a694294a82e1 Mon Sep 17 00:00:00 2001 From: Mark Erickson Date: Thu, 24 Sep 2015 09:11:34 -0500 Subject: [PATCH 53/53] Create an Apache Thrift language binding for Dart (dartlang.org). https://issues.apache.org/jira/browse/THRIFT-3299 Fixed Makefile dependencies and a little cleanup in TMessagaeReader. --- .../lib/src/transport/t_message_reader.dart | 20 ++++++++++++------- test/dart/Makefile.am | 16 +++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/lib/dart/lib/src/transport/t_message_reader.dart b/lib/dart/lib/src/transport/t_message_reader.dart index c7c47d0a84b..8ca070834ed 100644 --- a/lib/dart/lib/src/transport/t_message_reader.dart +++ b/lib/dart/lib/src/transport/t_message_reader.dart @@ -25,8 +25,8 @@ class TMessageReader { final int byteOffset; final _TMessageReaderTransport _transport; - /// Construct a [MessageReader]. The [readOffset] specifies the number of - /// bytes to skip before reading the [TMessage]. + /// Construct a [MessageReader]. The optional [byteOffset] specifies the + /// number of bytes to skip before reading the [TMessage]. TMessageReader(this.protocolFactory, {int byteOffset: 0}) : _transport = new _TMessageReaderTransport(), this.byteOffset = byteOffset; @@ -50,11 +50,17 @@ class _TMessageReaderTransport extends TTransport { void reset(Uint8List bytes, [int offset = 0]) { if (bytes == null) { _readIterator = null; - } else { - _readIterator = bytes.iterator; - for (var i = 0; i < offset; i++) { - _readIterator.moveNext(); - } + return; + } + + if (offset > bytes.length) { + throw new ArgumentError("The offset exceeds the bytes length"); + } + + _readIterator = bytes.iterator; + + for (var i = 0; i < offset; i++) { + _readIterator.moveNext(); } } diff --git a/test/dart/Makefile.am b/test/dart/Makefile.am index 4fd837fc3e4..59b3b7d720d 100755 --- a/test/dart/Makefile.am +++ b/test/dart/Makefile.am @@ -22,6 +22,12 @@ THRIFT = $(top_builddir)/compiler/cpp/thrift gen-dart/thrift_test/lib/thrift_test.dart: ../ThriftTest.thrift $(THRIFT) --gen dart ../ThriftTest.thrift +pub-get-gen: gen-dart/thrift_test/lib/thrift_test.dart + cd gen-dart/thrift_test; ${DARTPUB} get + +pub-get: pub-get-gen + cd test_client; ${DARTPUB} get + stubs: gen-dart/thrift_test/lib/thrift_test.dart pub-get precross: stubs @@ -29,15 +35,7 @@ precross: stubs check: stubs clean-local: - $(RM) -r gen-dart - -pub-get: pub-get-gen pub-get-client - -pub-get-gen: gen-dart/thrift_test/lib/thrift_test.dart - cd gen-dart/thrift_test; ${DARTPUB} get - -pub-get-client: - cd test_client; ${DARTPUB} get + $(RM) -r gen-dart test_client/.pub client: stubs ${DART} test_client/bin/main.dart