├── .Rbuildignore ├── .gitignore ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── .travis.yml ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── NEWS.md ├── R ├── parser.R ├── protocol_binary.R ├── rpc.R ├── server.R ├── thrift.R ├── transport.R ├── transport_buffered.R ├── transport_memory.R ├── transport_socket.R └── utils.R ├── README.md ├── demo ├── 00Index ├── calc │ ├── calc.thrift │ ├── calc_client.py │ └── calc_server.py ├── calc_client.R ├── calc_server.R ├── oneway │ ├── sleep.thrift │ ├── sleep_client.py │ └── sleep_server.py ├── ping │ ├── ping_client.py │ ├── ping_server.py │ └── pingpong.thrift ├── ping_client.R ├── ping_server.R ├── sleep_client.R └── sleep_server.R ├── man ├── TBinaryProtocol.Rd ├── TBinaryProtocolFactory.Rd ├── TBufferedTransport.Rd ├── TBufferedTransportFactory.Rd ├── TClient.Rd ├── TMemoryBuffer.Rd ├── TPayload.Rd ├── TServerSocket.Rd ├── TSocket.Rd ├── TType.Rd ├── binary_read_val.Rd ├── binary_write_val.Rd ├── hexlify.Rd ├── make_client.Rd ├── make_server.Rd ├── parse.Rd ├── parse_spec.Rd ├── t_load.Rd └── to_proper_struct.Rd ├── mvnw ├── mvnw.cmd ├── pom.xml ├── resources └── DESCRIPTION └── tests ├── testthat.R └── testthat ├── addressbook.thrift ├── base.thrift ├── const.thrift ├── container.thrift ├── multiplexed.thrift ├── parent.thrift ├── parser-cases ├── comments.thrift ├── constants.thrift ├── e_dead_include_0.thrift ├── e_dead_include_1.thrift ├── e_dead_include_2.thrift ├── e_dead_include_3.thrift ├── e_duplicate_field_id.thrift ├── e_duplicate_field_name.thrift ├── e_grammer_error_at_eof.thrift ├── e_service_extends_0.thrift ├── e_structs_0.thrift ├── e_structs_1.thrift ├── e_type_error_0.thrift ├── e_type_error_1.thrift ├── e_type_error_2.thrift ├── e_use_thrift_reserved_keywords.thrift ├── e_value_ref_0.thrift ├── e_value_ref_1.thrift ├── e_value_ref_2.thrift ├── enums.thrift ├── include.thrift ├── included.thrift ├── issue_215.thrift ├── recursive_union.thrift ├── service.thrift ├── service_extends.thrift ├── shared.thrift ├── structs.thrift ├── tutorial.thrift ├── type_ref.thrift ├── type_ref_shared.thrift ├── value_ref.thrift └── value_ref_shared.thrift ├── recursive_definition.thrift ├── storm.thrift ├── test.test_base.R ├── test.test_const.R ├── test.test_parser.R ├── test.test_protocol_binary.R ├── test.test_recursive_definition.R ├── test.test_socket.R ├── test.test_type.R ├── test.test_type_mismatch.R └── type.thrift /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^\.project$ 2 | ^\.settings$ 3 | ^\.gitignore$ 4 | ^\.idea 5 | ^\.travis.yml 6 | ^thriftr.code-workspace$ 7 | ^thriftr.iml 8 | ^pom.xml$ 9 | ^resources 10 | ^target$ 11 | ^.*\.Rproj$ 12 | ^\.Rproj\.user$ 13 | ^\.pydevproject$ 14 | ^\.vscode$ 15 | ^README.md$ 16 | ^NEWS.md$ 17 | ^\.mvn$ 18 | ^mvnw$ 19 | ^mvnw\.cmd$ 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.Rproj 3 | .Rproj.user 4 | .Rhistory 5 | .RData 6 | src/*.o 7 | src/*.so 8 | src/*.dll 9 | **/parser.out 10 | .project 11 | .settings 12 | .pydevproject 13 | thriftr.code-workspace 14 | .vscode/ 15 | .idea/ 16 | *.iml 17 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | wrapperVersion=3.3.4 2 | distributionType=only-script 3 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: r 2 | 3 | r_packages: 4 | - covr 5 | 6 | cache: packages 7 | 8 | after_success: 9 | - Rscript -e 'library(covr);codecov()' 10 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: thriftr 2 | Type: Package 3 | Title: Apache Thrift Client Server 4 | Version: 1.1.8 5 | Date: 2025-09-13 6 | Authors@R: c(person("Marek", "Jagielski", 7 | role = c("aut", "cre", "cph"), 8 | email = "marek.jagielski@gmail.com"), 9 | person("Lixin", "Yu", 10 | role = c("aut", "cph"))) 11 | Author: Marek Jagielski [aut, cre, cph], Lixin Yu [aut, cph] 12 | Maintainer: Marek Jagielski 13 | Description: Pure R implementation of Apache Thrift. 14 | This library doesn't require any code generation. 15 | To learn more about Thrift go to . 16 | License: MIT + file LICENSE 17 | URL: https://github.com/systemincloud/thriftr 18 | BugReports: https://github.com/systemincloud/thriftr/issues 19 | Suggests: testthat 20 | Encoding: UTF-8 21 | Imports: 22 | R6, 23 | rly, 24 | stringi 25 | RoxygenNote: 6.0.1 26 | Collate: 27 | 'thrift.R' 28 | 'transport.R' 29 | 'rpc.R' 30 | 'parser.R' 31 | 'protocol_binary.R' 32 | 'server.R' 33 | 'transport_buffered.R' 34 | 'transport_memory.R' 35 | 'transport_socket.R' 36 | 'utils.R' 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2018 2 | COPYRIGHT HOLDER: System in Cloud - Marek Jagielski -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(TBinaryProtocol) 4 | export(TBinaryProtocolFactory) 5 | export(TBufferedTransport) 6 | export(TBufferedTransportFactory) 7 | export(TClient) 8 | export(TMemoryBuffer) 9 | export(TPayload) 10 | export(TServerSocket) 11 | export(TSocket) 12 | export(TType) 13 | export(binary_read_val) 14 | export(binary_write_val) 15 | export(hexlify) 16 | export(make_client) 17 | export(make_server) 18 | export(parse_spec) 19 | export(t_load) 20 | export(to_proper_struct) 21 | importFrom(R6,R6Class) 22 | importFrom(rly,lex) 23 | importFrom(rly,yacc) 24 | importFrom(utils,head) 25 | importFrom(utils,tail) 26 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | ### Changes in [not yet released] 2 | 3 | ### Changes in v1.1.8 4 | 5 | #### NOTES 6 | 7 | 1. Fix CRAN notes: Add package anchors for R6Class cross-references in documentation 8 | 9 | ### Changes in v1.1.6 10 | 11 | #### NOTES 12 | 13 | 1. Resubmit. Dependency rly is fixed now. 14 | 15 | ### Changes in v1.1.5 16 | 17 | #### BUG FIXES 18 | 19 | 1. Fix #5 'Error in private$trans$read(sz)' 20 | 21 | ### Changes in v1.1.4 22 | 23 | #### BUG FIXES 24 | 25 | 1. Fix 'length > 1 in coercion to logical' 26 | 27 | ### Changes in v1.1.1 28 | 29 | #### BUG FIXES 30 | 31 | 1. Fix 'length > 1 in coercion to logical' 32 | 33 | ### Changes in v1.1.0 34 | 35 | #### NEW FEATURES 36 | 37 | 1. Handling circular type definitions 38 | 39 | #### BUG FIXES 40 | 41 | 1. Enums as default values in structs fails 42 | 43 | ### Changes in v1.0.3 44 | 45 | #### NOTES 46 | 47 | 1. Add example of how to use the library. -------------------------------------------------------------------------------- /R/parser.R: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2018 System in Cloud - Marek Jagielski 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | #' Load thrift file as a R6 instance. 24 | #' 25 | #' The module loaded and objects inside may only be pickled if module_name 26 | #' was provided. 27 | #' 28 | #' @param path file path to parse, should be a string ending with '.thrift' 29 | #' @param module_name the name for parsed module, the default is the basename 30 | #' without extension of `path` 31 | #' @param include_dirs directories to find thrift files while processing 32 | #' the `include` directive, by default: ['.'] 33 | #' 34 | #' @return Thrift R6 class instance 35 | #' 36 | #' @export 37 | t_load <- function(path, module_name=NA, include_dirs=NA) { 38 | thrift <- parse(path, module_name, include_dirs = include_dirs) 39 | if (length(names(incomplete_type$dict)) > 0) { 40 | fill_incomplete_ttype(thrift, thrift) 41 | } 42 | 43 | return(thrift) 44 | } 45 | 46 | fill_incomplete_ttype <- function(tmodule, definition) { 47 | type <- typeof(definition) 48 | clazz <- class(definition) 49 | 50 | # fill incomplete ttype 51 | if (type == "list") { 52 | # fill const value 53 | if (typeof(definition[[1]]) == "character" && definition[[1]] == 'UNKNOWN_CONST') { 54 | ttype <- get_definition( 55 | tmodule, incomplete_type$dict[[as.character(definition[[2]])]][[1]], definition[[4]]) 56 | return(Parser$new()$cast(ttype)(definition[[3]])) 57 | } 58 | # fill an incomplete alias ttype 59 | if (typeof(definition[[2]]) == "character" && definition[[2]] %in% names(incomplete_type$dict)) { 60 | it <- incomplete_type$dict[[definition[[2]]]] 61 | return(list(definition[[1]], get_definition(tmodule, it[[1]], it[[2]]))) 62 | } 63 | # fill service method which has incomplete arg, return ttype 64 | else if (typeof(definition[[1]]) == "character" && definition[[1]] %in% names(incomplete_type$dict)) { 65 | it <- incomplete_type$dict[[as.character(definition[[1]])]] 66 | real_type <- get_definition(tmodule, it[[1]], it[[2]]) 67 | return(list(real_type[[1]], definition[[2]], real_type[[2]], definition[[3]])) 68 | } 69 | # fill incomplete compound ttype 70 | else if (typeof(definition[[2]]) == "list") { 71 | return(list(definition[[1]], fill_incomplete_ttype(tmodule, definition[[2]]))) 72 | } 73 | } 74 | # handle thrift module 75 | else if (type == "environment" && 76 | length(clazz) > 1 && 77 | clazz[[2]] == "thrift") { 78 | for (name in names(definition)) { 79 | # skip inner attribute 80 | if (name %in% c( 81 | "thrift_meta", 82 | "thrift_file", 83 | ".__enclos_env__", 84 | "clone", 85 | "add_public")) { 86 | next 87 | } 88 | attr <- definition[[name]] 89 | definition[[name]] <- fill_incomplete_ttype(definition, attr) 90 | } 91 | } 92 | # handle struct ttype 93 | else if (type == "environment" && 94 | length(clazz) > 0 && 95 | clazz[[1]] == "R6ClassGenerator" && 96 | !is.null(definition$inherit) && 97 | definition$inherit == "TPayload") { 98 | for (index in names(definition$thrift_spec)) { 99 | value <- definition$thrift_spec[[index]] 100 | # if the ttype of field is incomplete 101 | if (as.character(value[[1]]) %in% names(incomplete_type$dict)) { 102 | it <- incomplete_type$dict[[as.character(value[[1]])]] 103 | real_type <- fill_incomplete_ttype(tmodule, get_definition(tmodule, it[[1]], it[[2]])) 104 | # if deletion ttype is a compound type 105 | if (typeof(real_type) == "list") { 106 | definition$thrift_spec[[index]] <- list(real_type[[1]], value[[2]],real_type[[2]], value[[3]]) 107 | } 108 | # if deletion ttype is a built-in ttype 109 | else { 110 | definition$thrift_spec[[index]] <- append( 111 | list(fill_incomplete_ttype(tmodule, get_definition(tmodule, it[[1]], it[[2]]))), 112 | head(value, 1)) 113 | } 114 | } 115 | # if the ttype which field's ttype contains is incomplete 116 | else if (typeof(value[[3]]) == "integer" && as.character(value[[3]]) %in% names(incomplete_type$dict)) { 117 | it <- incomplete_type$dict[[as.character(value[[3]])]] 118 | definition$thrift_spec[[index]] <- list( 119 | value[[1]], 120 | value[[2]], 121 | fill_incomplete_ttype( 122 | tmodule, 123 | get_definition( 124 | tmodule, 125 | it[[1]], 126 | it[[2]]) 127 | ), 128 | value[[4]]) 129 | } 130 | } 131 | } 132 | # handle service method 133 | else if (type == "environment" && 134 | length(clazz) > 0 && 135 | clazz[[1]] == "R6ClassGenerator" && 136 | !(is.null(definition$thrift_services))) { 137 | for (name in names(definition)) { 138 | attr <- definition[[name]] 139 | if (typeof(attr) != "environment" || (typeof(attr) == "environment" && is.null(attr$thrift_spec))) { 140 | next 141 | } 142 | for (index in names(attr$thrift_spec)) { 143 | value <- attr$thrift_spec[[index]] 144 | attr$thrift_spec[[index]] <- fill_incomplete_ttype(tmodule, value) 145 | } 146 | } 147 | } 148 | return(definition) 149 | } 150 | 151 | get_definition <- function(thrift, name, lineno) { 152 | ref_type <- thrift 153 | for (n in strsplit(name, "\\.")[[1]]) { 154 | ref_type = thrift[[n]] 155 | if (is.null(ref_type)) { 156 | stop(sprintf('No type found: %s, at line %d', name, lineno)) 157 | } 158 | 159 | if (typeof(ref_type) == "integer" && ref_type < 0) { 160 | stop(sprintf('No type found: %s, at line %d', incomplete_type$dict$get(ref_type), lineno)) 161 | } 162 | 163 | if (!is.null(ref_type$ttype)) { 164 | return(list(ref_type$ttype, ref_type)) 165 | } else { 166 | return(ref_type) 167 | } 168 | } 169 | } 170 | 171 | 172 | # lexer 173 | 174 | LITERALS <- ":;,=*{}()<>[]" 175 | 176 | THRIFT_RESERVED_KEYWORDS <- c( 177 | "BEGIN", 178 | "END", 179 | "__CLASS__", 180 | "__DIR__", 181 | "__FILE__", 182 | "__FUNCTION__", 183 | "__LINE__", 184 | "__METHOD__", 185 | "__NAMESPACE__", 186 | "abstract", 187 | "alias", 188 | "and", 189 | "args", 190 | "as", 191 | "assert", 192 | "begin", 193 | "break", 194 | "case", 195 | "catch", 196 | "class", 197 | "clone", 198 | "continue", 199 | "declare", 200 | "def", 201 | "default", 202 | "del", 203 | "delete", 204 | "do", 205 | "dynamic", 206 | "elif", 207 | "else", 208 | "elseif", 209 | "elsif", 210 | "end", 211 | "enddeclare", 212 | "endfor", 213 | "endforeach", 214 | "endif", 215 | "endswitch", 216 | "endwhile", 217 | "ensure", 218 | "except", 219 | "exec", 220 | "finally", 221 | "float", 222 | "for", 223 | "foreach", 224 | "function", 225 | "global", 226 | "goto", 227 | "if", 228 | "implements", 229 | "import", 230 | "in", 231 | "inline", 232 | "instanceof", 233 | "interface", 234 | "is", 235 | "lambda", 236 | "module", 237 | "native", 238 | "new", 239 | "next", 240 | "nil", 241 | "not", 242 | "or", 243 | "pass", 244 | "public", 245 | "print", 246 | "private", 247 | "protected", 248 | "public", 249 | "raise", 250 | "redo", 251 | "rescue", 252 | "retry", 253 | "register", 254 | "return", 255 | "self", 256 | "sizeof", 257 | "static", 258 | "super", 259 | "switch", 260 | "synchronized", 261 | "then", 262 | "this", 263 | "throw", 264 | "transient", 265 | "try", 266 | "undef", 267 | "union", 268 | "unless", 269 | "unsigned", 270 | "until", 271 | "use", 272 | "var", 273 | "virtual", 274 | "volatile", 275 | "when", 276 | "while", 277 | "with", 278 | "xor", 279 | "yield" 280 | ) 281 | 282 | KEYWORDS <- c( 283 | "namespace", 284 | "include", 285 | "void", 286 | "bool", 287 | "byte", 288 | "i16", 289 | "i32", 290 | "i64", 291 | "double", 292 | "string", 293 | "binary", 294 | "map", 295 | "list", 296 | "set", 297 | "oneway", 298 | "typedef", 299 | "struct", 300 | "union", 301 | "exception", 302 | "extends", 303 | "throws", 304 | "service", 305 | "enum", 306 | "const", 307 | "required", 308 | "optional" 309 | ) 310 | 311 | TOKENS <- c(c( 312 | "BOOLCONSTANT", 313 | "INTCONSTANT", 314 | "DUBCONSTANT", 315 | "LITERAL", 316 | "IDENTIFIER"), 317 | toupper(KEYWORDS) 318 | ) 319 | 320 | Lexer <- R6::R6Class("Lexer", 321 | public = list( 322 | tokens = TOKENS, 323 | literals = LITERALS, 324 | t_ignore = " \t\r", 325 | t_error = function(t) { 326 | stop(sprintf("Illegal character %s at line %d", t$value[1], t$lineno)) 327 | }, 328 | t_newline = function(re="\\n+", t) { 329 | t$lexer$lineno <- t$lexer$lineno + nchar(t$value) 330 | return(NULL) 331 | }, 332 | t_ignore_SILLYCOMM = function(re="\\/\\*\\**\\*\\/", t) { 333 | t$lexer$lineno <- t$lexer$lineno + 334 | lengths(regmatches(t$value, gregexpr("\n", t$value))) 335 | return(NULL) 336 | }, 337 | t_ignore_MULTICOMM = function( 338 | re="\\/\\*[^*]\\/*([^*/]|[^*]\\/|\\*[^/])*\\**\\*\\/", t) { 339 | t$lexer$lineno <- t$lexer$lineno + 340 | lengths(regmatches(t$value, gregexpr("\n", t$value))) 341 | return(NULL) 342 | }, 343 | t_ignore_DOCTEXT = function( 344 | re="\\/\\*\\*([^*/]|[^*]\\/|\\*[^/])*\\**\\*\\/", t) { 345 | t$lexer$lineno <- t$lexer$lineno + 346 | lengths(regmatches(t$value, gregexpr("\n", t$value))) 347 | return(NULL) 348 | }, 349 | t_ignore_UNIXCOMMENT = function(re="\\#[^\\n]*", t) { 350 | }, 351 | t_ignore_COMMENT = function(re="\\/\\/[^\\n]*", t) { 352 | }, 353 | t_BOOLCONSTANT = function(re="\\btrue\\b|\\bfalse\\b", t) { 354 | t$value <- t$value == "true" 355 | return(t) 356 | }, 357 | t_DUBCONSTANT = function(re="-?\\d+\\.\\d*(e-?\\d+)?", t) { 358 | t$value <- as.numeric(t$value) 359 | return(t) 360 | }, 361 | t_HEXCONSTANT = function(re="0x[0-9A-Fa-f]+", t) { 362 | t$value <- strtoi(t$value) 363 | t$type <- "INTCONSTANT" 364 | return(t) 365 | }, 366 | t_INTCONSTANT = function(re="[+-]?[0-9]+", t) { 367 | t$value <- strtoi(t$value) 368 | return(t) 369 | }, 370 | t_LITERAL = function( 371 | re='(\\"([^\\\n]|(\\.))*?\")|\'([^\\\n]|(\\.))*?\'', t) { 372 | s <- substr(t$value, 2, nchar(t$value) - 1) 373 | maps <- new.env(hash = TRUE) 374 | maps[["t"]] <- "\t" 375 | maps[["r"]] <- "\r" 376 | maps[["n"]] <- "\n" 377 | maps[["\\"]] <- "\\" 378 | maps[["\'"]] <- "\'" 379 | maps[['"']] <- '\"' 380 | 381 | i <- 1 382 | length <- nchar(s) 383 | val <- "" 384 | while (i <= length) { 385 | if (substr(s, i, i) == "\\") { 386 | i <- i + 1 387 | if (substr(s, i, i) %in% maps) val <- val + maps[[substr(s, i, i)]] 388 | else { 389 | msg <- sprintf("Unexcepted escaping character: %s", substr(s, i, i)) 390 | stop("[ThriftLexerError]", msg) 391 | } 392 | } else val <- paste(val, substr(s, i, i), sep = "") 393 | i <- i + 1 394 | } 395 | 396 | t$value <- val 397 | return(t) 398 | }, 399 | t_IDENTIFIER = function(re="[a-zA-Z_](\\.[a-zA-Z_0-9]|[a-zA-Z_0-9])*", t) { 400 | if (t$value %in% KEYWORDS) { 401 | t$type <- toupper(t$value) 402 | return(t) 403 | } 404 | if (t$value %in% THRIFT_RESERVED_KEYWORDS) 405 | stop("[ThriftLexerError]", sprintf("Cannot use reserved language keyword: %s at line %d", 406 | t$value, t$lineno)) 407 | return(t) 408 | } 409 | ) 410 | ) 411 | 412 | 413 | CurrentIncompleteType <- R6::R6Class("CurrentIncompleteType", 414 | public = list( 415 | dict = new.env(hash = TRUE), 416 | index = as.integer(-1), 417 | set_info = function(info) { 418 | self$dict[[as.character(self$index)]] <- info 419 | self$index <- self$index - 1 420 | return(as.integer(self$index + 1)) 421 | } 422 | ) 423 | ) 424 | 425 | 426 | incomplete_type <- CurrentIncompleteType$new() 427 | 428 | 429 | # parser 430 | 431 | #' @importFrom utils tail 432 | Parser <- R6::R6Class("Parser", 433 | public = list( 434 | thrift_stack = list(), 435 | include_dirs_ = list("."), 436 | thrift_cache = new.env(hash = TRUE), 437 | 438 | tokens = TOKENS, 439 | p_error = function(p) { 440 | if (is.null(p)) stop("[ThriftGrammerError]", "Grammar error at EOF") 441 | else stop("[ThriftGrammerError]", sprintf("Grammar error %s at '%s'", p$value, p$lineno)) 442 | }, 443 | p_start = function(doc="start : header definition", p) { 444 | }, 445 | p_header = function(doc='header : header_unit_ header 446 | |', p) { 447 | }, 448 | p_header_unit_ = function(doc="header_unit_ : header_unit ';' 449 | | header_unit", p) { 450 | }, 451 | p_header_unit = function(doc='header_unit : include 452 | | namespace', p) { 453 | }, 454 | p_include = function(doc="include : INCLUDE LITERAL", p) { 455 | thrift <- tail(Parser$thrift_stack, 1)[[1]] 456 | if (is.null(thrift$thrift_file)) 457 | stop("[ThriftParserError]", "Unexcepted include statement while loading from file like object.") 458 | 459 | replace_include_dirs <- if (dirname(thrift$thrift_file) != ".") 460 | append(Parser$include_dirs_, dirname(thrift$thrift_file), 0) 461 | else Parser$include_dirs_ 462 | for (include_dir in replace_include_dirs) { 463 | path <- file.path(include_dir, p$get(3)) 464 | if (file.exists(path)) { 465 | child <- parse(path) 466 | thrift[[class(child)[[1]]]] <- child 467 | private$add_thrift_meta("includes", child) 468 | return() 469 | } 470 | } 471 | stop("[ThriftParserError]", sprintf("Couldn't include thrift %s in any directories provided", p$get(3))) 472 | }, 473 | p_namespace = function(doc='namespace : NAMESPACE namespace_scope IDENTIFIER', p) { 474 | # namespace is useless in thriftpy 475 | # if p[2] == 'py' or p[2] == '*': 476 | # setattr(thrift_stack[-1], '__name__', p[3]) 477 | }, 478 | p_namespace_scope = function(doc='namespace_scope : "*" 479 | | IDENTIFIER', p) { 480 | p$set(1, p$get(2)) 481 | }, 482 | p_sep = function(doc='sep : "," 483 | | ";"', p) { 484 | }, 485 | p_definition = function(doc='definition : definition definition_unit_ 486 | |', p) { 487 | }, 488 | p_definition_unit_ = function(doc='definition_unit_ : definition_unit ";" 489 | | definition_unit', p) { 490 | }, 491 | p_definition_unit = function(doc='definition_unit : const 492 | | ttype', p) { 493 | }, 494 | p_const = function( 495 | doc='const : CONST field_type IDENTIFIER "=" const_value 496 | | CONST field_type IDENTIFIER "=" const_value sep', p) { 497 | val <- tryCatch({ 498 | self$cast(p$get(3), p$lineno(4))(p$get(6)) 499 | }, error = function(e) { 500 | if (!grepl("\\[AssertionError\\]", e[[1]])) stop(e[[1]]) 501 | stop("[ThriftParserError]", sprintf("Type error for constant %s at line %d", p$get(4), p$lineno(4))) 502 | }) 503 | 504 | tail(Parser$thrift_stack, 1)[[1]]$add_public(p$get(4), val) 505 | private$add_thrift_meta('consts', val) 506 | }, 507 | p_const_value = function(doc='const_value : INTCONSTANT 508 | | DUBCONSTANT 509 | | LITERAL 510 | | BOOLCONSTANT 511 | | const_list 512 | | const_map 513 | | const_ref', p) { 514 | p$set(1, p$get(2)) 515 | }, 516 | p_const_list = function(doc='const_list : "[" const_list_seq "]" ', p) { 517 | p$set(1, p$get(3)) 518 | }, 519 | p_const_list_seq = function( 520 | doc = "const_list_seq : const_value sep const_list_seq 521 | | const_value const_list_seq 522 | |", p) { 523 | private$parse_seq(p) 524 | }, 525 | p_const_map = function(doc='const_map : "{" const_map_seq "}" ', p) { 526 | dict <- new.env(hash = TRUE) 527 | for (it in p$get(3)) { 528 | dict[[toString(it[[1]])]] <- it[[2]] 529 | } 530 | p$set(1, dict) 531 | }, 532 | p_const_map_seq = function( 533 | doc = "const_map_seq : const_map_item sep const_map_seq 534 | | const_map_item const_map_seq 535 | |", p) { 536 | private$parse_seq(p) 537 | }, 538 | p_const_map_item = function( 539 | doc = 'const_map_item : const_value ":" const_value ', p) { 540 | p$set(1, list(p$get(2), p$get(4))) 541 | }, 542 | p_const_ref = function(doc = "const_ref : IDENTIFIER", p) { 543 | child <- tail(Parser$thrift_stack, 1)[[1]] 544 | for (name in strsplit(p$get(2), "\\.")[[1]]) { 545 | father <- child 546 | child <- child[[name]] 547 | if (is.null(child)) 548 | stop("[ThriftParserError]", 549 | sprintf("Can\'t find name %s at line %d", p$get(2), p$lineno(2))) 550 | } 551 | 552 | father_type <- private$get_ttype(father) 553 | if (is.null(private$get_ttype(child)) || 554 | (!is.null(father_type) && father_type == TType$I32)) { 555 | # child is a constant or enum value 556 | p$set(1, child) 557 | } else stop("[ThriftParserError]", 558 | sprintf("No enum value or constant found named %s", p$get(2))) 559 | }, 560 | p_ttype = function(doc = "ttype : typedef 561 | | enum 562 | | struct 563 | | union 564 | | exception 565 | | service", p) { 566 | }, 567 | p_typedef = function(doc = "typedef : TYPEDEF field_type IDENTIFIER", p) { 568 | tail(Parser$thrift_stack, 1)[[1]]$add_public(p$get(4), p$get(3)) 569 | }, 570 | p_enum = function(doc='enum : ENUM IDENTIFIER "{" enum_seq "}" ', p) { 571 | val <- private$make_enum(p$get(3), p$get(5)) 572 | tail(Parser$thrift_stack, 1)[[1]]$add_public(p$get(3), val) 573 | private$add_thrift_meta('enums', val) 574 | }, 575 | p_enum_seq = function(doc="enum_seq : enum_item sep enum_seq 576 | | enum_item enum_seq 577 | |", p) { 578 | private$parse_seq(p) 579 | }, 580 | p_enum_item = function(doc='enum_item : IDENTIFIER "=" INTCONSTANT 581 | | IDENTIFIER 582 | |', p) { 583 | if (p$length() == 4) p$set(1, list(p$get(2), p$get(4))) 584 | else if (p$length() == 2) p$set(1, list(p$get(2), NULL)) 585 | }, 586 | p_struct = function(doc='struct : seen_struct "{" field_seq "}" ', p) { 587 | val <- private$fill_in_struct(p$get(2), p$get(4)) 588 | private$add_thrift_meta("structs", val) 589 | }, 590 | p_seen_struct = function(doc="seen_struct : STRUCT IDENTIFIER ", p) { 591 | val <- private$make_empty_struct(p$get(3)) 592 | tail(Parser$thrift_stack, 1)[[1]]$add_public(p$get(3), val) 593 | p$set(1, val) 594 | }, 595 | p_union = function(doc='union : seen_union "{" field_seq "}" ', p) { 596 | val <- private$fill_in_struct(p$get(2), p$get(4)) 597 | private$add_thrift_meta("unions", val) 598 | }, 599 | p_seen_union = function(doc="seen_union : UNION IDENTIFIER ", p) { 600 | val <- private$make_empty_struct(p$get(3)) 601 | tail(Parser$thrift_stack, 1)[[1]]$add_public(p$get(3), val) 602 | p$set(1, val) 603 | }, 604 | p_exception = function(doc='exception : EXCEPTION IDENTIFIER "{" field_seq "}" ', p) { 605 | val <- private$make_struct(p$get(3), p$get(5)) 606 | tail(Parser$thrift_stack, 1)[[1]]$add_public(p$get(3), val) 607 | private$add_thrift_meta("exceptions", val) 608 | }, 609 | p_service = function(doc='service : SERVICE IDENTIFIER "{" function_seq "}" 610 | | SERVICE IDENTIFIER EXTENDS IDENTIFIER "{" function_seq "}"', p) { 611 | thrift <- tail(Parser$thrift_stack, 1)[[1]] 612 | 613 | if (p$length() == 8) { 614 | extends <- thrift 615 | for (name in strsplit(p$get(5), "\\.")[[1]]) { 616 | extends <- extends[[name]] 617 | if (is.null(extends)) 618 | stop("[ThriftParserError]", sprintf("Can't find service %s for service %s to extend", 619 | p$get(5), p$get(3))) 620 | } 621 | 622 | if (is.null(extends[['thrift_services']])) 623 | stop(sprintf('Can\'t extends %s, not a service', p$get(5))) 624 | 625 | } else extends <- NULL 626 | 627 | val <- private$make_service(p$get(3), p$get(p$length() - 1), extends) 628 | thrift[[p$get(3)]] <- val 629 | private$add_thrift_meta('services', val) 630 | }, 631 | p_function = function( 632 | doc='function : ONEWAY function_type IDENTIFIER "(" field_seq ")" throws 633 | | ONEWAY function_type IDENTIFIER "(" field_seq ")" 634 | | function_type IDENTIFIER "(" field_seq ")" throws 635 | | function_type IDENTIFIER "(" field_seq ")" ', p) { 636 | oneway <- NA 637 | base <- NA 638 | throws <- NA 639 | if (p$get(2)[[1]] == 'oneway') { 640 | oneway <- TRUE 641 | base <- 2 642 | } else { 643 | oneway <- FALSE 644 | base <- 1 645 | } 646 | 647 | if (p$get(p$length()) == ')') throws <- list() 648 | else throws <- p$get(p$length()) 649 | 650 | p$set(1, list(oneway, p$get(base + 1), p$get(base + 2), p$get(base + 4), throws)) 651 | }, 652 | p_function_seq = function(doc='function_seq : function sep function_seq 653 | | function function_seq 654 | |', p) { 655 | private$parse_seq(p) 656 | }, 657 | p_throws = function(doc='throws : THROWS "(" field_seq ")" ', p) { 658 | p$set(1, p$get(4)) 659 | }, 660 | p_function_type = function(doc='function_type : field_type 661 | | VOID', p) { 662 | if(p$get(2)[[1]] == 'void') p$set(1, TType$VOID) 663 | else p$set(1, p$get(2)) 664 | }, 665 | p_field_seq = function(doc='field_seq : field sep field_seq 666 | | field field_seq 667 | |', p) { 668 | private$parse_seq(p) 669 | }, 670 | p_field = function(doc='field : field_id field_req field_type IDENTIFIER 671 | | field_id field_req field_type IDENTIFIER "=" const_value', p) { 672 | val <- NA 673 | if (p$length() == 7) { 674 | val <- tryCatch({ 675 | self$cast(p$get(4))(p$get(7)) 676 | }, error = function(e) { 677 | }) 678 | 679 | if (is.null(val)) 680 | stop(sprintf('Type error for field %s at line %d', p$get(5), p$lineno(5))) 681 | } 682 | 683 | p$set(1, list(p$get(2), p$get(3), p$get(4), p$get(5), val)) 684 | }, 685 | p_field_id = function(doc='field_id : INTCONSTANT ":" ', p) { 686 | p$set(1, p$get(2)) 687 | }, 688 | p_field_req = function(doc='field_req : REQUIRED 689 | | OPTIONAL 690 | |', p) { 691 | if (p$length() == 2) p$set(1, p$get(2) == 'required') 692 | else if (p$length() == 1) p$set(1, FALSE) # default: required=False 693 | }, 694 | p_field_type = function(doc='field_type : ref_type 695 | | definition_type', p) { 696 | p$set(1, p$get(2)) 697 | }, 698 | p_ref_type = function(doc='ref_type : IDENTIFIER', p) { 699 | ref_type <- tail(Parser$thrift_stack, 1)[[1]] 700 | 701 | index <- 0 702 | for (name in strsplit(p$get(2), "\\.")[[1]]) { 703 | index <- index + 1 704 | ref_type <- ref_type[[name]] 705 | if (is.null(ref_type)) { 706 | if (index != (length(strsplit(p$get(2), "\\.")[[1]]))) { 707 | stop(sprintf('No type found: %s, at line %d', p$get(2), p$lineno(2))) 708 | } 709 | p$set(1, incomplete_type$set_info(list(p$get(2), p$lineno(2)))) 710 | return() 711 | } 712 | } 713 | 714 | if (typeof(ref_type) == 'environment' && 715 | !is.null(ref_type$ttype)) p$set(1, list(ref_type$ttype, ref_type)) 716 | else p$set(1, ref_type) 717 | }, 718 | p_base_type = function(doc='base_type : BOOL 719 | | BYTE 720 | | I16 721 | | I32 722 | | I64 723 | | DOUBLE 724 | | STRING 725 | | BINARY', p) { 726 | if (p$get(2) == 'bool') p$set(1, TType$BOOL) 727 | if (p$get(2) == 'byte') p$set(1, TType$BYTE) 728 | if (p$get(2) == 'i16') p$set(1, TType$I16) 729 | if (p$get(2) == 'i32') p$set(1, TType$I32) 730 | if (p$get(2) == 'i64') p$set(1, TType$I64) 731 | if (p$get(2) == 'double') p$set(1, TType$DOUBLE) 732 | if (p$get(2) == 'string') p$set(1, TType$STRING) 733 | if (p$get(2) == 'binary') p$set(1, TType$BINARY) 734 | }, 735 | p_container_type = function(doc='container_type : map_type 736 | | list_type 737 | | set_type', p) { 738 | p$set(1, p$get(2)) 739 | }, 740 | p_map_type = function(doc='map_type : MAP "<" field_type "," field_type ">" ', p) { 741 | p$set(1, list(TType$MAP, list(p$get(4), p$get(6)))) 742 | }, 743 | p_list_type = function(doc='list_type : LIST "<" field_type ">" ', p) { 744 | p$set(1, list(TType$LIST, p$get(4))) 745 | }, 746 | p_set_type = function(doc='set_type : SET "<" field_type ">" ', p) { 747 | p$set(1, list(TType$SET, p$get(4))) 748 | }, 749 | p_definition_type = function(doc='definition_type : base_type 750 | | container_type', p) { 751 | p$set(1, p$get(2)) 752 | }, 753 | cast = function(t, linno=0) { 754 | if (is.integer(t) && t < 0) { 755 | return(private$lazy_cast_const(t, linno)) 756 | } else if (typeof(t) != "list") { 757 | if (t == TType$BOOL) return(private$cast_bool) 758 | if (t == TType$BYTE) return(private$cast_byte) 759 | if (t == TType$I16) return(private$cast_i16) 760 | if (t == TType$I32) return(private$cast_i32) 761 | if (t == TType$I64) return(private$cast_i64) 762 | if (t == TType$DOUBLE) return(private$cast_double) 763 | if (t == TType$STRING) return(private$cast_string) 764 | if (t == TType$BINARY) return(private$cast_binary) 765 | } else { 766 | if (t[[1]] == TType$LIST) return(private$cast_list(t)) 767 | if (t[[1]] == TType$SET) return(private$cast_set(t)) 768 | if (t[[1]] == TType$MAP) return(private$cast_map(t)) 769 | if (t[[1]] == TType$I32) return(private$cast_enum(t)) 770 | if (t[[1]] == TType$STRUCT) return(private$cast_struct(t)) 771 | } 772 | } 773 | ), 774 | private = list( 775 | add_thrift_meta = function(key, val) { 776 | thrift <- tail(Parser$thrift_stack, 1)[[1]] 777 | 778 | if (is.null(thrift$thrift_meta)) { 779 | thrift$add_public('thrift_meta', new.env(hash = TRUE)) 780 | } 781 | if (is.null(thrift$thrift_meta[[key]])) { 782 | thrift$thrift_meta[[key]] <- list() 783 | } 784 | thrift$thrift_meta[[key]] <- append(thrift$thrift_meta[[key]], val) 785 | }, 786 | parse_seq = function(p) { 787 | if (p$length() == 4) p$set(1, append(list(p$get(2)), p$get(4))) 788 | else if (p$length() == 3) p$set(1, append(list(p$get(2)), p$get(3))) 789 | else if (p$length() == 1) p$set(1, list()) 790 | }, 791 | lazy_cast_const = function(t, linno) { 792 | inner_cast <- function(v) { 793 | return(list('UNKNOWN_CONST', t, v, linno)) 794 | } 795 | return(inner_cast) 796 | }, 797 | cast_bool = function(v) { 798 | if (typeof(v) != "logical" && typeof(v) != "integer") stop("[AssertionError]") 799 | return(as.logical(v)) 800 | }, 801 | cast_byte = function(v) { 802 | # TODO 803 | }, 804 | cast_i16 = function(v) { 805 | if (typeof(v) != "integer") stop("[AssertionError]") 806 | return(v) 807 | }, 808 | cast_i32 = function(v) { 809 | if (typeof(v) != "integer") stop("[AssertionError]") 810 | return(v) 811 | }, 812 | cast_i64 = function(v) { 813 | if (typeof(v) != "integer") stop("[AssertionError]") 814 | return(v) 815 | }, 816 | cast_double = function(v) { 817 | if (typeof(v) != "double") stop("[AssertionError]") 818 | return(v) 819 | }, 820 | cast_string = function(v) { 821 | if (typeof(v) != "character") stop("[AssertionError]") 822 | return(v) 823 | }, 824 | cast_binary = function(v) { 825 | # TODO 826 | }, 827 | cast_list = function(t) { 828 | if (t[[1]] != TType$LIST) stop("[AssertionError]") 829 | 830 | cast_list_ <- function(v) { 831 | if (typeof(v) != "list") stop("[AssertionError]") 832 | v <- lapply(v, self$cast(t[[2]])) 833 | return(v) 834 | } 835 | return(cast_list_) 836 | }, 837 | cast_set = function(t) { 838 | if (t[[1]] != TType$SET) stop("[AssertionError]") 839 | 840 | cast_set_ <- function(v) { 841 | if (typeof(v) != "list") stop("[AssertionError]") 842 | v <- lapply(v, self$cast(t[[2]])) 843 | return(v) 844 | } 845 | return(cast_set_) 846 | }, 847 | cast_map = function(t) { 848 | if (t[[1]] != TType$MAP) stop("[AssertionError]") 849 | 850 | cast_map_ <- function(v) { 851 | if (typeof(v) != "environment") stop("[AssertionError]") 852 | for (key in names(v)) { 853 | v[[toString(self$cast(t[[2]][[1]])(key))]] <- 854 | self$cast(t[[2]][[2]])(v[[key]]) 855 | } 856 | return(v) 857 | } 858 | return(cast_map_) 859 | }, 860 | cast_enum = function(t) { 861 | if (t[[1]] != TType$I32) stop("[AssertionError]") 862 | 863 | cast_enum_ <- function(v) { 864 | if (typeof(v) == "character") v <- strtoi(v) 865 | if (typeof(v) != "integer") stop("[AssertionError]") 866 | if (v %in% lapply(ls(t[[2]]), function(x) t[[2]][[x]])) 867 | return(v) 868 | stop("[ThriftParserError]", 869 | sprintf("Couldn't find a named value in enum %s for value %d", 870 | class(t[[2]])[[1]], v)) 871 | } 872 | return(cast_enum_) 873 | }, 874 | cast_struct = function(t) { # struct/exception/union 875 | if (t[[1]] != TType$STRUCT) stop("[AssertionError]") 876 | 877 | cast_struct_ <- function(v) { 878 | if (class(v)[[1]] == t[[2]]$classname) 879 | return(v) # already cast 880 | 881 | if (typeof(v) != "environment") stop("[AssertionError]") 882 | tspec <- t[[2]]$tspec 883 | 884 | for (key in names(tspec)) { # requirement check 885 | if (tspec[[key]][[1]] && !(key %in% names(v))) { 886 | stop("[ThriftParserError]", 887 | sprintf("Field %s was required to create constant for type %s", 888 | key, t[[2]]$classname)) 889 | } 890 | } 891 | 892 | # cast values 893 | for (key in names(v)) { 894 | if (!(key %in% names(tspec))) { 895 | stop("[ThriftParserError]", 896 | sprintf("No field named %s was found in struct of type %s", 897 | key, t[[2]]$classname)) 898 | } 899 | v[[key]] <- self$cast(tspec[[key]][[2]])(v[[key]]) 900 | } 901 | args <- list() 902 | for (name in names(v)) { 903 | args[[name]] <- v[[name]] 904 | } 905 | return(do.call(t[[2]]$new, args)) 906 | } 907 | return(cast_struct_) 908 | }, 909 | make_enum = function(name, kvs) { 910 | cls <- R6::R6Class( 911 | name, 912 | inherit = TPayload, 913 | lock_objects = FALSE, 914 | public = list( 915 | module = tail(Parser$thrift_stack, 1)[[1]]$name, 916 | ttype = TType$I32 917 | ) 918 | ) 919 | cls$ttype <- TType$I32 920 | 921 | values_to_names <- new.env(hash = TRUE) 922 | names_to_values <- new.env(hash = TRUE) 923 | 924 | if (!is.null(kvs) && length(kvs) > 0) { 925 | val <- kvs[[1]][[2]] 926 | if (is.null(val)) val <- -1 927 | i <- 1 928 | for (item in kvs) { 929 | if (is.null(item[[2]])) { 930 | kvs[[i]][[2]] <- as.integer(val + 1) 931 | } 932 | val <- kvs[[i]][[2]] 933 | i <- i + 1 934 | } 935 | for (key_val in kvs) { 936 | key <- key_val[[1]] 937 | val <- key_val[[2]] 938 | cls$set("public", key, val) 939 | values_to_names[[as.character(val)]] <- key 940 | names_to_values[[key]] <- val 941 | } 942 | } 943 | cls$set("public", "VALUES_TO_NAMES", values_to_names) 944 | cls$set("public", "NAMES_TO_VALUES", names_to_values) 945 | return(cls$new()) 946 | }, 947 | make_empty_struct = function(name, ttype=TType$STRUCT) { 948 | cls <- R6::R6Class( 949 | name, 950 | inherit = TPayload, 951 | lock_objects = FALSE, 952 | public = list( 953 | module = class(tail(Parser$thrift_stack, 1)[[1]])[[1]] 954 | ) 955 | ) 956 | cls$ttype <- ttype 957 | return(cls) 958 | }, 959 | fill_in_struct = function(cls, fields, gen_init=TRUE) { 960 | thrift_spec <- new.env(hash = TRUE) 961 | default_spec <- list() 962 | tspec <- new.env(hash = TRUE) 963 | 964 | for (field in fields) { 965 | if (as.character(field[[1]]) %in% names(thrift_spec) || field[[4]] %in% names(tspec)) 966 | stop("[ThriftGrammerError]", sprintf("'%d:%s\' field identifier/name has already been used", 967 | field[[1]], field[[4]])) 968 | ttype <- field[[3]] 969 | thrift_spec[[as.character(field[[1]])]] <- private$ttype_spec(ttype, field[[4]], field[[2]]) 970 | default_spec[[length(default_spec)+1]] <- list(field[[4]], field[[5]]) 971 | tspec[[field[[4]]]] <- list(field[[2]], ttype) 972 | } 973 | cls$set("public", 'thrift_spec', thrift_spec) 974 | cls$thrift_spec <- thrift_spec 975 | cls$set("public", 'default_spec', default_spec) 976 | cls$default_spec <- default_spec 977 | cls$set("public", 'tspec', tspec) 978 | cls$tspec <- tspec 979 | if (gen_init) gen_init(cls, thrift_spec, default_spec) 980 | return(cls) 981 | }, 982 | make_struct = function(name, fields, ttype=TType$STRUCT, gen_init=TRUE) { 983 | cls <- private$make_empty_struct(name, ttype = ttype) 984 | return(private$fill_in_struct(cls, fields, gen_init = gen_init)) 985 | }, 986 | make_service = function(name, funcs, extends) { 987 | if (is.null(extends)) { 988 | extends <- R6::R6Class() 989 | } 990 | 991 | # print(class(tail(Parser$thrift_stack, 1)[[1]])[[1]]) 992 | cls <- R6::R6Class( 993 | name, 994 | # # inherit=extends, 995 | lock_objects = FALSE, 996 | public = list( 997 | # module=class(tail(Parser$thrift_stack, 1)[[1]])[[1]], 998 | ) 999 | ) 1000 | thrift_services <- list() 1001 | 1002 | for (func in funcs) { 1003 | func_name <- func[[3]] 1004 | # args payload cls 1005 | args_name <- sprintf('%s_args', func_name) 1006 | args_fields <- func[[4]] 1007 | args_cls <- private$make_struct(args_name, args_fields) 1008 | cls$set('public', args_name, args_cls) 1009 | cls[[args_name]] <- args_cls 1010 | # result payload cls 1011 | result_name <- sprintf('%s_result', func_name) 1012 | result_type <- func[[2]] 1013 | result_throws <- func[[5]] 1014 | result_oneway <- func[[1]] 1015 | result_cls <- private$make_struct(result_name, result_throws, gen_init=FALSE) 1016 | result_cls$set('public', 'oneway', result_oneway) 1017 | if (typeof(result_type) == 'list' || result_type != TType$VOID) { 1018 | result_cls$thrift_spec[['0']] <- private$ttype_spec(result_type, 'success') 1019 | result_cls$default_spec <- append(list(list('success', NA)), result_cls$default_spec) 1020 | } 1021 | gen_init(result_cls, result_cls$thrift_spec, result_cls$default_spec) 1022 | cls$set('public', result_name, result_cls) 1023 | cls[[result_name]] <- result_cls 1024 | thrift_services <- append(thrift_services, func_name) 1025 | } 1026 | if (!is.null(extends) && !is.null(extends$thrift_services)) { 1027 | thrift_services <- append(thrift_services, extends$thrift_services) 1028 | } 1029 | cls$set('public', 'thrift_services', thrift_services) 1030 | cls$thrift_services <- thrift_services 1031 | return(cls) 1032 | }, 1033 | ttype_spec = function(ttype, name, required=FALSE) { 1034 | if (is.integer(ttype)) return(list(ttype, name, required)) 1035 | else return(list(ttype[[1]], name, ttype[[2]], required)) 1036 | }, 1037 | get_ttype = function(inst, default_ttype=NULL) { 1038 | if (typeof(inst) == "environment" && 1039 | !is.null(inst$ttype)) return(inst$ttype) 1040 | else return(default_ttype) 1041 | } 1042 | ) 1043 | ) 1044 | Parser$thrift_stack = list() 1045 | Parser$include_dirs_ = list(".") 1046 | Parser$thrift_cache = new.env(hash = TRUE) 1047 | 1048 | 1049 | #' Parse a single thrift file to R6 class instance 1050 | #' 1051 | #' @importFrom R6 R6Class 1052 | #' @importFrom rly lex 1053 | #' @importFrom rly yacc 1054 | #' @importFrom utils head 1055 | #' 1056 | #' @param path file path to parse, should be a string ending with '.thrift' 1057 | #' @param module_name the name for parsed module, the default is the basename 1058 | #' without extension of `path` 1059 | #' @param include_dirs directories to find thrift files while processing 1060 | #' the `include` directive, by default: ['.'] 1061 | #' @param lexer rly lexer to use, if not provided, `parse` will use a new one 1062 | #' @param parser rly parser to use, if not provided, `parse` will use a new one 1063 | #' @param enable_cache if this is set to be `TRUE`, parsed module will be 1064 | #' cached, this is enabled by default. If `module_name` 1065 | #' is provided, use it as cache key, else use the `path` 1066 | #' 1067 | #' @return Thrift module 1068 | parse <- function( 1069 | path, 1070 | module_name=NA, 1071 | include_dirs=NA, 1072 | lexer=NA, 1073 | parser=NA, 1074 | enable_cache=TRUE) { 1075 | 1076 | # dead include checking on current stack 1077 | for (thrift in Parser$thrift_stack) { 1078 | if (!is.null(thrift$thrift_file) && path == thrift$thrift_file) 1079 | stop("[ThriftParserError]", sprintf("Dead including on %s", path)) 1080 | } 1081 | 1082 | cache_key <- if (is.na(module_name)) path else module_name 1083 | 1084 | if (enable_cache && cache_key %in% names(Parser$thrift_cache)) 1085 | return(Parser$thrift_cache[[cache_key]]) 1086 | 1087 | if (is.na(lexer)) lexer <- rly::lex(Lexer) 1088 | if (is.na(parser)) parser <- rly::yacc(Parser) 1089 | 1090 | if (!is.na(include_dirs)) Parser$include_dirs_ <- include_dirs 1091 | 1092 | if (!endsWith(path, ".thrift")) 1093 | stop("[ThriftParserError]", "Path should end with .thrift") 1094 | 1095 | if (startsWith(path, "http://") || startsWith(path, "https://")) 1096 | data <- url(path, "r+") 1097 | else 1098 | data <- readChar(path, file.info(path)$size) 1099 | 1100 | if (!is.na(module_name) && !endsWith(module_name, "_thrift")) 1101 | stop("[ThriftParserError]", "ThriftPy can only generate module with '_thrift' suffix") 1102 | 1103 | if (is.na(module_name)) { 1104 | module_name <- strsplit(basename(path), "\\.")[[1]] 1105 | } 1106 | 1107 | thrift <- R6::R6Class( 1108 | module_name, 1109 | lock_objects = FALSE, 1110 | public = list(thrift_file = path, 1111 | add_public = function(name, obj) { 1112 | self[[name]] <- obj 1113 | } 1114 | ) 1115 | )$new() 1116 | Parser$thrift_stack <- append(Parser$thrift_stack, thrift) 1117 | lexer$lineno <- 1 1118 | parser$parse(data, lexer) 1119 | Parser$thrift_stack <- head(Parser$thrift_stack, -1) 1120 | 1121 | if (enable_cache) Parser$thrift_cache[[cache_key]] <- thrift 1122 | 1123 | return(thrift) 1124 | } 1125 | -------------------------------------------------------------------------------- /R/protocol_binary.R: -------------------------------------------------------------------------------- 1 | 2 | VERSION_MASK = -65536 3 | VERSION_1 <- -2147418112 4 | TYPE_MASK = 0x000000ff 5 | 6 | pack_i8 <- function(byte) { 7 | as.raw(strtoi(strsplit(gsub("(.{2})", "\\1 ", as.character(format(as.hexmode(byte), width = 2))), " ")[[1]], 16L)) 8 | } 9 | 10 | pack_i16 <- function(i16) { 11 | as.raw(strtoi(strsplit(gsub("(.{2})", "\\1 ", as.character(format(as.hexmode(i16), width = 4))), " ")[[1]], 16L)) 12 | } 13 | 14 | pack_i32 <- function(i32) { 15 | as.raw(strtoi(strsplit(gsub("(.{2})", "\\1 ", as.character(format(as.hexmode(i32), width = 8))), " ")[[1]], 16L)) 16 | } 17 | 18 | pack_i64 <- function(i64) { 19 | as.raw( 20 | strtoi( 21 | c( 22 | strsplit(gsub("(.{2})", "\\1 ", as.character(format(as.hexmode(i64[[1]]), width = 4))), " ")[[1]], 23 | strsplit(gsub("(.{2})", "\\1 ", as.character(format(as.hexmode(i64[[2]]), width = 4))), " ")[[1]] 24 | ), 25 | 16L) 26 | ) 27 | } 28 | 29 | pack_double <- function(dub) { 30 | writeBin(dub, raw(), size = 8, endian = "big") 31 | } 32 | 33 | pack_string <- function(string) { 34 | b <- charToRaw(string) 35 | c(pack_i32(length(b)), b) 36 | } 37 | 38 | unpack_i8 <- function(buf) { 39 | readBin(buf, integer(), 1, size = 1, signed = TRUE) 40 | } 41 | 42 | unpack_i16 <- function(buf) { 43 | readBin(buf, integer(), 1, size = 2, signed = TRUE, endian="big") 44 | } 45 | 46 | unpack_i32 <- function(buf) { 47 | readBin(buf, integer(), 1, size = 4, signed = TRUE, endian="big") 48 | } 49 | 50 | unpack_i64 <- function(buf) { 51 | readBin(buf, integer(), 2, size = 4, signed = TRUE, endian="big") 52 | } 53 | 54 | unpack_double <- function(buf) { 55 | readBin(buf, numeric(), 1, size = 8, signed = TRUE, endian="big") 56 | } 57 | 58 | write_message_begin = function(outbuf, name, ttype, seqid, strict = TRUE) { 59 | if (strict) { 60 | outbuf$write(c(pack_i32(bitwOr(VERSION_1, ttype)))) 61 | outbuf$write(pack_string(name)) 62 | } else { 63 | outbuf$write(pack_string(name)) 64 | outbuf$write(pack_i8(ttype)) 65 | } 66 | outbuf$write(pack_i32(seqid)) 67 | } 68 | 69 | write_field_begin = function(outbuf, ttype, fid) { 70 | outbuf$write(c(pack_i8(ttype), pack_i16(fid))) 71 | } 72 | 73 | 74 | write_field_stop = function(outbuf) { 75 | outbuf$write(pack_i8(TType$STOP)) 76 | } 77 | 78 | write_list_begin = function(outbuf, etype, size) { 79 | outbuf$write(c(pack_i8(etype), pack_i32(size))) 80 | } 81 | 82 | write_map_begin = function(outbuf, ktype, vtype, size) { 83 | outbuf$write(c(pack_i8(ktype), pack_i8(vtype), pack_i32(size))) 84 | } 85 | 86 | #' Binary protocol: write value to binary buffer 87 | #' 88 | #' @param outbuf binary buffor 89 | #' @param ttype type of value 90 | #' @param val value to write 91 | #' @param spec specification of value 92 | #' 93 | #' @export 94 | binary_write_val <- function(outbuf, ttype, val, spec = NA) { 95 | if (ttype == TType$BOOL) { 96 | if (val) { 97 | outbuf$write(pack_i8(1)) 98 | } else { 99 | outbuf$write(pack_i8(0)) 100 | } 101 | } else if (ttype == TType$BYTE) { 102 | outbuf$write(pack_i8(val)) 103 | } else if (ttype == TType$I16) { 104 | outbuf$write(pack_i16(val)) 105 | } else if (ttype == TType$I32) { 106 | outbuf$write(pack_i32(val)) 107 | } else if (ttype == TType$I64) { 108 | outbuf$write(pack_i64(val)) 109 | } else if (ttype == TType$DOUBLE) { 110 | outbuf$write(pack_double(val)) 111 | } else if (ttype == TType$STRING) { 112 | outbuf$write(pack_string(val)) 113 | } else if (ttype == TType$SET || ttype == TType$LIST) { 114 | if (length(spec) == 2) { 115 | e_type <- spec[[1]] 116 | t_spec <- spec[[2]] 117 | } else { 118 | e_type <- spec[[1]] 119 | t_spec <- NA 120 | } 121 | 122 | val_len <- length(val) 123 | write_list_begin(outbuf, e_type, val_len) 124 | for (e_val in val) { 125 | binary_write_val(outbuf, e_type, e_val, t_spec) 126 | } 127 | } else if (ttype == TType$MAP) { 128 | if (typeof(spec[[1]]) == "integer") { 129 | k_type <- spec[[1]] 130 | k_spec <- NA 131 | } else { 132 | k_type <- spec[[1]][[1]] 133 | k_spec <- spec[[1]][[2]] 134 | } 135 | 136 | if (typeof(spec[[2]]) == "integer") { 137 | v_type <- spec[[2]] 138 | v_spec <- NA 139 | } else { 140 | v_type <- spec[[2]][[1]] 141 | v_spec = spec[[2]][[2]] 142 | } 143 | 144 | write_map_begin(outbuf, k_type, v_type, length(val)) 145 | for (k in names(val)) { 146 | binary_write_val(outbuf, k_type, k, k_spec) 147 | binary_write_val(outbuf, v_type, val[[k]], v_spec) 148 | } 149 | } else if (ttype == TType$STRUCT) { 150 | for (fid in names(val$thrift_spec)) { 151 | f_spec <- val$thrift_spec[[fid]] 152 | if (length(f_spec) == 3) { 153 | f_type <- f_spec[[1]] 154 | f_name <- f_spec[[2]] 155 | f_req <- f_spec[[3]] 156 | f_container_spec <- NA 157 | } else { 158 | f_type <- f_spec[[1]] 159 | f_name <- f_spec[[2]] 160 | f_container_spec <- f_spec[[3]] 161 | f_req <- f_spec[[4]] 162 | } 163 | 164 | v <- val[[f_name]] 165 | if (length(v) == 1 && is.na(v)) next 166 | 167 | write_field_begin(outbuf, f_type, fid) 168 | binary_write_val(outbuf, f_type, v, f_container_spec) 169 | } 170 | 171 | write_field_stop(outbuf) 172 | } 173 | } 174 | 175 | read_message_begin <- function(inbuf, strict = TRUE) { 176 | sz <- unpack_i32(inbuf$read(4)) 177 | if (sz < 0) { 178 | version <- bitwAnd(sz, VERSION_MASK) 179 | if (version != VERSION_1) { 180 | stop(sprintf("[TProtocolException][BAD_VERSION] Bad version in read_message_begin: %d'", sz)) 181 | } 182 | 183 | name_sz <- unpack_i32(inbuf$read(4)) 184 | name <- rawToChar(inbuf$read(name_sz)) 185 | 186 | type_ <- bitwAnd(sz, TYPE_MASK) 187 | } else { 188 | if (strict) { 189 | stop("[TProtocolException][BAD_VERSION] No protocol version header") 190 | } 191 | name <- rawToChar(inbuf$read(sz)) 192 | type_ <- unpack_i8(inbuf$read(1)) 193 | } 194 | 195 | seqid <- unpack_i32(inbuf$read(4)) 196 | 197 | return(list(name, type_, seqid)) 198 | } 199 | 200 | read_field_begin <- function(inbuf) { 201 | f_type <- unpack_i8(inbuf$read(1)) 202 | if (f_type == TType$STOP) return(list(f_type, 0, inbuf)) 203 | return(list(f_type, unpack_i16(inbuf$read(2)))) 204 | } 205 | 206 | read_list_begin <- function(inbuf) { 207 | e_type <- unpack_i8(inbuf$read(1)) 208 | sz <- unpack_i32(inbuf$read(4)) 209 | return(list(e_type, sz)) 210 | } 211 | 212 | read_map_begin = function(inbuf) { 213 | k_type <- unpack_i8(inbuf$read(1)) 214 | v_type <- unpack_i8(inbuf$read(1)) 215 | sz <- unpack_i32(inbuf$read(4)) 216 | return(list(k_type, v_type, sz)) 217 | } 218 | 219 | #' Binary protocol: read value from binary buffer 220 | #' 221 | #' @param inbuf binary buffor 222 | #' @param ttype type of value 223 | #' @param spec specification of value 224 | #' @param decode_response for string decode binary as chars 225 | #' 226 | #' @return value of type ttype 227 | #' 228 | #' @export 229 | binary_read_val <- function(inbuf, ttype, spec = NA, decode_response = TRUE) { 230 | if (ttype == TType$BOOL) { 231 | return(as.logical(unpack_i8(inbuf$read(1)))) 232 | } else if (ttype == TType$BYTE) { 233 | return(unpack_i8(inbuf$read(1))) 234 | } else if (ttype == TType$I16) { 235 | return(unpack_i16(inbuf$read(2))) 236 | } else if (ttype == TType$I32) { 237 | return(unpack_i32(inbuf$read(4))) 238 | } else if (ttype == TType$I64) { 239 | return(unpack_i64(inbuf$read(8))) 240 | } else if (ttype == TType$DOUBLE) { 241 | return(unpack_double(inbuf$read(8))) 242 | } else if (ttype == TType$STRING) { 243 | sz <- unpack_i32(inbuf$read(4)) 244 | byte_payload <- '' 245 | if (sz > 0) { 246 | byte_payload <- inbuf$read(sz) 247 | } 248 | 249 | # Since we cannot tell if we're getting STRING or BINARY 250 | # if not asked not to decode, try both 251 | if (decode_response && sz > 0) { 252 | return(stringi::stri_encode(rawToChar(byte_payload), from = "UTF-8", to = "UTF-8")) 253 | # TODO 254 | } else return(byte_payload) 255 | } else if (ttype == TType$SET || ttype == TType$LIST) { 256 | if (length(spec) == 2) { 257 | v_type <- spec[[1]] 258 | v_spec <- spec[[2]] 259 | } else { 260 | v_type <- spec[[1]] 261 | v_spec <- NA 262 | } 263 | 264 | result <- list() 265 | r_type_sz <- read_list_begin(inbuf) 266 | r_type <- r_type_sz[[1]] 267 | sz <- r_type_sz[[2]] 268 | 269 | if (sz == 0) { 270 | return(list()) 271 | } 272 | 273 | # the v_type is useless here since we already get it from spec 274 | if (r_type != v_type) { 275 | for (i in 1:sz) { 276 | skip(inbuf, r_type) 277 | } 278 | return(list()) 279 | } 280 | 281 | for(i in 1:sz) { 282 | result[[length(result) + 1]] <- binary_read_val(inbuf, v_type, v_spec, decode_response) 283 | } 284 | return(result) 285 | } else if (ttype == TType$MAP) { 286 | if (typeof(spec[[1]]) == "integer") { 287 | k_type <- spec[[1]] 288 | k_spec <- NA 289 | } else { 290 | k_type <- spec[[1]][[1]] 291 | k_spec <- spec[[1]][[2]] 292 | } 293 | 294 | if (typeof(spec[[2]]) == "integer") { 295 | v_type <- spec[[2]] 296 | v_spec <- NA 297 | } else { 298 | v_type <- spec[[2]][[1]] 299 | v_spec <- spec[[2]][[2]] 300 | } 301 | 302 | result <- new.env(hash=TRUE) 303 | sk_type_sv_type_sz <- read_map_begin(inbuf) 304 | sk_type <- sk_type_sv_type_sz[[1]] 305 | sv_type <- sk_type_sv_type_sz[[2]] 306 | sz <- sk_type_sv_type_sz[[3]] 307 | 308 | if (sz == 0) { 309 | return(result) 310 | } 311 | 312 | if (sk_type != k_type || sv_type != v_type) { 313 | for (i in 1:sz) { 314 | skip(inbuf, sk_type) 315 | skip(inbuf, sv_type) 316 | return(new.env(hash=TRUE)) 317 | } 318 | } 319 | 320 | for (i in 1:sz) { 321 | k_val <- binary_read_val(inbuf, k_type, k_spec, decode_response) 322 | v_val <- binary_read_val(inbuf, v_type, v_spec, decode_response) 323 | result[[as.character(k_val)]] <- v_val 324 | } 325 | 326 | return(result) 327 | } else if (ttype == TType$STRUCT) { 328 | obj <- spec$new() 329 | read_struct(inbuf, obj, decode_response) 330 | return(obj) 331 | } 332 | } 333 | 334 | read_struct <- function(inbuf, obj, decode_response=TRUE) { 335 | while (TRUE) { 336 | f_type_fid <- read_field_begin(inbuf) 337 | f_type <- f_type_fid[[1]] 338 | fid <- f_type_fid[[2]] 339 | 340 | if (f_type == TType$STOP) break 341 | 342 | if (!(fid %in% names(obj$thrift_spec))) { 343 | skip(inbuf, f_type) 344 | next 345 | } 346 | 347 | if (length(obj$thrift_spec[[as.character(fid)]]) == 3) { 348 | sf_type_f_name_f_req <- obj$thrift_spec[[as.character(fid)]] 349 | sf_type <- sf_type_f_name_f_req[[1]] 350 | f_name <- sf_type_f_name_f_req[[2]] 351 | f_container_spec <- NA 352 | } else { 353 | sf_type_f_name_f_container_spec_f_req <- obj$thrift_spec[[as.character(fid)]] 354 | sf_type <- sf_type_f_name_f_container_spec_f_req[[1]] 355 | f_name <- sf_type_f_name_f_container_spec_f_req[[2]] 356 | f_container_spec <- sf_type_f_name_f_container_spec_f_req[[3]] 357 | f_req <- sf_type_f_name_f_container_spec_f_req[[4]] 358 | } 359 | 360 | # it really should equal here. but since we already wasted 361 | # space storing the duplicate info, let's check it. 362 | if (f_type != sf_type) { 363 | skip(inbuf, f_type) 364 | next 365 | } 366 | 367 | val <- binary_read_val(inbuf, f_type, f_container_spec, decode_response) 368 | obj[[f_name]] <- val 369 | } 370 | } 371 | 372 | skip <- function(inbuf, ftype) { 373 | if (ftype == TType$BOOL || ftype == TType$BYTE) 374 | inbuf$read(1) 375 | else if (ftype == TType$I16) 376 | inbuf$read(2) 377 | else if (ftype == TType$I32) 378 | inbuf$read(4) 379 | else if (ftype == TType$I64) 380 | inbuf$read(8) 381 | else if (ftype == TType$DOUBLE) 382 | inbuf$read(8) 383 | else if (ftype == TType$STRING) { 384 | inbuf$read(unpack_i32(inbuf$read(4))) 385 | } 386 | else if (ftype == TType$SET || ftype == TType$LIST) { 387 | v_type_sz <- read_list_begin(inbuf) 388 | for (i in 1:v_type_sz[[2]]) { 389 | skip(inbuf, v_type_sz[[1]]) 390 | } 391 | } 392 | else if (ftype == TType$MAP) { 393 | k_type_v_type_sz <- read_map_begin(inbuf) 394 | for (i in 1:k_type_v_type_sz[[3]]) { 395 | skip(inbuf, k_type_v_type_sz[[1]]) 396 | skip(inbuf, k_type_v_type_sz[[2]]) 397 | } 398 | } 399 | else if (ftype == TType$STRUCT) { 400 | while (TRUE) { 401 | f_type_fid <- read_field_begin(inbuf) 402 | if (f_type_fid[[1]] == TType$STOP) break 403 | skip(inbuf, f_type_fid[[1]]) 404 | } 405 | } 406 | } 407 | 408 | 409 | #' TBinaryProtocol 410 | #' 411 | #' Binary implementation of the Thrift protocol driver. 412 | #' 413 | #' @docType class 414 | #' @importFrom R6 R6Class 415 | #' @format An \code{\link{R6Class}} generator object 416 | #' 417 | #' @export 418 | TBinaryProtocol <- R6Class("TBinaryProtocol", 419 | public = list( 420 | trans = NA, 421 | strict_read = NA, 422 | strict_write = NA, 423 | decode_response = NA, 424 | initialize = function(trans, strict_read = TRUE, strict_write = TRUE, decode_response = TRUE) { 425 | self$trans <- trans 426 | self$strict_read <- strict_read 427 | self$strict_write <- strict_write 428 | self$decode_response <- decode_response 429 | }, 430 | skip = function(ttype) { 431 | skip(self$trans, ttype) 432 | }, 433 | read_message_begin = function() { 434 | api_ttype_seqid <- read_message_begin(self$trans, strict = self$strict_read) 435 | return(api_ttype_seqid) 436 | }, 437 | read_message_end = function() { 438 | }, 439 | write_message_begin = function(name, ttype, seqid) { 440 | write_message_begin(self$trans, name, ttype, seqid, strict = self$strict_write) 441 | }, 442 | write_message_end = function() { 443 | }, 444 | read_struct = function(obj) { 445 | return(read_struct(self$trans, obj, self$decode_response)) 446 | }, 447 | write_struct = function(obj) { 448 | return(binary_write_val(self$trans, TType$STRUCT, obj)) 449 | } 450 | ) 451 | ) 452 | 453 | #' TBinaryProtocolFactory 454 | #' 455 | #' TBinaryProtocolFactory generates TBinaryProtocol driver. 456 | #' 457 | #' @docType class 458 | #' @importFrom R6 R6Class 459 | #' @format An \code{\link{R6Class}} generator object 460 | #' 461 | #' @export 462 | TBinaryProtocolFactory <- R6Class("TBinaryProtocolFactory", 463 | public = list( 464 | strict_read = NA, 465 | strict_write = NA, 466 | decode_response = NA, 467 | initialize = function(strict_read=TRUE, strict_write=TRUE, decode_response=TRUE) { 468 | self$strict_read <- strict_read 469 | self$strict_write <- strict_write 470 | self$decode_response <- decode_response 471 | }, 472 | get_protocol = function(trans) { 473 | return(TBinaryProtocol$new(trans, self$strict_read, 474 | self$strict_write, 475 | self$decode_response)) 476 | } 477 | ) 478 | ) -------------------------------------------------------------------------------- /R/rpc.R: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2016 System in Cloud - Marek Jagielski 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | #' Create client side thrift API 24 | #' 25 | #' @param service parsed service 26 | #' @param host server host 27 | #' @param port server tcp port 28 | #' @param proto_factory factory that generates protocol implementation 29 | #' @param trans_factory factory that generates transport implementation 30 | #' 31 | #' @export 32 | #' 33 | #' @examples 34 | #' \dontrun{ 35 | #' # File calc.thrift content: 36 | #' # service Calculator { 37 | #' # i32 add(1:i32 a, 2:i32 b); 38 | #' # i32 sub(1:i32 a, 2:i32 b); 39 | #' # i32 mult(1:i32 a, 2:i32 b); 40 | #' # i32 div(1:i32 a, 2:i32 b); 41 | #' # } 42 | #' # 43 | #' 44 | #' calc_thrift <- thriftr::t_load("calc.thrift", module_name="calc_thrift") 45 | #' 46 | #' cal <- thriftr::make_client( 47 | #' calc_thrift$Calculator, 48 | #' "127.0.0.1", 49 | #' 6000) 50 | #' 51 | #' a <- cal$mult(5, 2) 52 | #' b <- cal$sub(7, 3) 53 | #' c <- cal$sub(6, 4) 54 | #' d <- cal$mult(b, 10) 55 | #' e <- cal$add(a, d) 56 | #' f <- cal$div(e, c) 57 | #' print(f) 58 | #' } 59 | make_client = function(service, 60 | host="localhost", 61 | port=9090, 62 | proto_factory=TBinaryProtocolFactory$new(), 63 | trans_factory=TBufferedTransportFactory$new()) { 64 | 65 | socket <- NA 66 | if(!is.na(host) || !is.na(port)) { 67 | socket <- TSocket$new(host, port) 68 | } else stop("Host/port must be provided.") 69 | 70 | transport <- trans_factory$get_transport(socket) 71 | protocol <- proto_factory$get_protocol(transport) 72 | transport$open() 73 | 74 | return(TClient$new(service, protocol)) 75 | } 76 | 77 | #' Create server side thrift API 78 | #' 79 | #' @param service parsed service 80 | #' @param handler R6 class implementing service 81 | #' @param host server host 82 | #' @param port port server tcp port 83 | #' @param proto_factory factory that generates protocol implementation 84 | #' @param trans_factory factory that generates transport implementation 85 | #' 86 | #' @export 87 | #' 88 | #' @examples 89 | #' \dontrun{ 90 | #' # File calc.thrift content: 91 | #' # service Calculator { 92 | #' # i32 add(1:i32 a, 2:i32 b); 93 | #' # i32 sub(1:i32 a, 2:i32 b); 94 | #' # i32 mult(1:i32 a, 2:i32 b); 95 | #' # i32 div(1:i32 a, 2:i32 b); 96 | #' # } 97 | #' # 98 | #' 99 | #' calc_thrift <- thriftr::t_load("calc.thrift", module_name="calc_thrift") 100 | #' 101 | #' Dispatcher <- R6::R6Class("Dispatcher", 102 | #' public = list( 103 | #' add = function(a, b) { 104 | #' print(sprintf("add -> %s + %s", a, b)) 105 | #' return(a + b) 106 | #' }, 107 | #' sub = function(a, b) { 108 | #' print(sprintf("sub -> %s - %s", a, b)) 109 | #' return(a - b) 110 | #' }, 111 | #' mult = function(a, b) { 112 | #' print(sprintf("mult -> %s * %s", a, b)) 113 | #' return(a * b) 114 | #' }, 115 | #' div = function(a, b) { 116 | #' print(sprintf("div -> %s / %s", a, b)) 117 | #' return(a / b) 118 | #' } 119 | #' ) 120 | #' ) 121 | #' 122 | #' server <- thriftr::make_server( 123 | #' calc_thrift$Calculator, 124 | #' Dispatcher$new(), 125 | #' "127.0.0.1", 126 | #' 6000) 127 | #' 128 | #' print("serving...") 129 | #' 130 | #' server$serve() 131 | #' } 132 | make_server = function(service, 133 | handler, 134 | host="localhost", 135 | port=9090, 136 | proto_factory=TBinaryProtocolFactory$new(), 137 | trans_factory=TBufferedTransportFactory$new()) { 138 | processor <- TProcessor$new(service, handler) 139 | 140 | server_socket <- TServerSocket$new( 141 | host=host, port=port) 142 | 143 | server <- TSimpleServer$new(processor, server_socket, 144 | iprot_factory=proto_factory, 145 | itrans_factory=trans_factory) 146 | 147 | return(server) 148 | } -------------------------------------------------------------------------------- /R/server.R: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2016 System in Cloud - Marek Jagielski 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | TServer <- R6Class("TServer", 24 | public = list( 25 | processor = NA, 26 | trans = NA, 27 | itrans_factory = NA, 28 | iprot_factory = NA, 29 | otrans_factory = NA, 30 | oprot_factory = NA, 31 | initialize = function( 32 | processor, 33 | trans, 34 | itrans_factory = NULL, 35 | iprot_factory = NULL, 36 | otrans_factory = NULL, 37 | oprot_factory = NULL) { 38 | self$processor <- processor 39 | self$trans <- trans 40 | 41 | self$itrans_factory <- itrans_factory 42 | if (is.null(self$itrans_factory)) { 43 | self$itrans_factory <- TBufferedTransportFactory$new() 44 | } 45 | self$iprot_factory <- iprot_factory 46 | if (is.null(self$iprot_factory)) { 47 | self$iprot_factory <- TBinaryProtocolFactory$new() 48 | } 49 | self$otrans_factory <- otrans_factory 50 | if (is.null(self$otrans_factory)) { 51 | self$otrans_factory <- itrans_factory 52 | } 53 | self$oprot_factory <- oprot_factory 54 | if (is.null(self$oprot_factory)) { 55 | self$oprot_factory <- iprot_factory 56 | } 57 | }, 58 | serve = function() { 59 | }, 60 | close = function() { 61 | } 62 | ) 63 | ) 64 | 65 | TSimpleServer <- R6Class("TSimpleServer", 66 | inherit = TServer, 67 | public = list( 68 | closed = NA, 69 | initialize = function( 70 | processor = NULL, 71 | trans = NULL, 72 | itrans_factory = NULL, 73 | iprot_factory = NULL, 74 | otrans_factory = NULL, 75 | oprot_factory = NULL) { 76 | super$initialize( 77 | processor, 78 | trans, 79 | itrans_factory, 80 | iprot_factory, 81 | otrans_factory, 82 | oprot_factory 83 | ) 84 | self$closed <- FALSE 85 | }, 86 | serve = function() { 87 | self$trans$listen() 88 | while (TRUE) { 89 | self$closed <- FALSE 90 | client <- self$trans$accept() 91 | itrans <- self$itrans_factory$get_transport(client) 92 | otrans <- self$otrans_factory$get_transport(client) 93 | iprot <- self$iprot_factory$get_protocol(itrans) 94 | oprot <- self$oprot_factory$get_protocol(otrans) 95 | 96 | while (!self$closed) { 97 | tryCatch({ 98 | self$processor$process(iprot, oprot) 99 | }, error = function(e) { 100 | self$close() 101 | }) 102 | } 103 | 104 | itrans$close() 105 | otrans$close() 106 | } 107 | }, 108 | close = function() { 109 | self$closed <- TRUE 110 | } 111 | ) 112 | ) 113 | -------------------------------------------------------------------------------- /R/thrift.R: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2016 System in Cloud - Marek Jagielski 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | #' parse_spec 24 | #' 25 | #' String representation of specification 26 | #' 27 | #' @param ttype type 28 | #' @param spec specification 29 | #' 30 | #' @return string representation 31 | #' 32 | #' @export 33 | parse_spec = function(ttype, spec = NA) { 34 | name_map <- as.list(TType) 35 | name_map_tmp <- name_map 36 | name_map <- names(name_map) 37 | names(name_map) <- unlist(name_map_tmp, use.names=FALSE) 38 | 39 | type_ = function(s) { 40 | if (is.list(s)) { 41 | return(parse_spec(s[[1]], s[[2]])) 42 | } else { 43 | return(name_map[[as.character(s)]]) 44 | } 45 | } 46 | 47 | if (length(spec) == 1 && is.na(spec)) { 48 | return(name_map[[as.character(ttype)]]) 49 | } 50 | 51 | if (ttype == TType$STRUCT) { 52 | return(spec$classname) 53 | } 54 | 55 | if (ttype %in% c(TType$LIST, TType$SET)) { 56 | return(sprintf("%s<%s>", name_map[[as.character(ttype)]], type_(spec))) 57 | } 58 | 59 | if (ttype == TType$MAP) { 60 | return(sprintf("MAP<%s, %s>", type_(spec[[1]]), type_(spec[[2]]))) 61 | } 62 | } 63 | 64 | # init_func_generator 65 | # 66 | # Generate `initialize` function based on TPayload$default_spec 67 | # 68 | # @param cls R6 class 69 | # @param spec specification 70 | # 71 | # @return constructor function 72 | init_func_generator = function(cls, spec) { 73 | if(length(spec) == 0) return(function() { }) 74 | 75 | args <- alist() 76 | for(s in spec) { 77 | args[[s[[1]]]] <- s[[2]] 78 | } 79 | 80 | func <- function() { 81 | argg <- as.list(environment()) 82 | for(arg_name in names(argg)) { 83 | self[[arg_name]] <- argg[[arg_name]] 84 | } 85 | } 86 | formals(func) <- args 87 | 88 | return(func) 89 | } 90 | 91 | #' TType 92 | #' 93 | #' Identificator of value type. 94 | #' 95 | #' @export 96 | TType <- new.env(hash=TRUE) 97 | TType$STOP <- as.integer(0) 98 | TType$VOID <- as.integer(1) 99 | TType$BOOL <- as.integer(2) 100 | TType$BYTE <- as.integer(3) 101 | TType$I08 <- as.integer(3) 102 | TType$DOUBLE <- as.integer(4) 103 | TType$I16 <- as.integer(6) 104 | TType$I32 <- as.integer(8) 105 | TType$I64 <- as.integer(10) 106 | TType$STRING <- as.integer(11) 107 | TType$UTF7 <- as.integer(11) 108 | TType$BINARY <- as.integer(11) # This here just for parsing. For all purposes, it's a string 109 | TType$STRUCT <- as.integer(12) 110 | TType$MAP <- as.integer(13) 111 | TType$SET <- as.integer(14) 112 | TType$LIST <- as.integer(15) 113 | TType$UTF8 <- as.integer(16) 114 | TType$UTF16 <- as.integer(17) 115 | 116 | # TMessageType 117 | TMessageType <- new.env(hash=TRUE) 118 | TMessageType$CALL <- as.integer(1) 119 | TMessageType$REPLY <- as.integer(2) 120 | TMessageType$EXCEPTION <- as.integer(3) 121 | TMessageType$ONEWAY <- as.integer(4) 122 | 123 | 124 | gen_init = function(cls, thrift_spec=NULL, default_spec=NULL) { 125 | if(!is.null(thrift_spec)) cls$thrift_spec <- thrift_spec 126 | if(!is.null(default_spec)) 127 | cls$set('public', 'initialize', init_func_generator(cls, default_spec)) 128 | return(cls) 129 | } 130 | 131 | #' TPayload 132 | #' 133 | #' Base class for all complex types of api. 134 | #' 135 | #' @docType class 136 | #' @importFrom R6 R6Class 137 | #' @format An \code{\link{R6Class}} generator object 138 | #' 139 | #' @export 140 | TPayload <- R6Class("TPayload", 141 | public = list( 142 | read = function(iprot) { 143 | iprot$read_struct(self) 144 | }, 145 | write = function(oprot) { 146 | oprot$write_struct(self) 147 | } 148 | ) 149 | ) 150 | 151 | #' TClient 152 | #' 153 | #' TClient implements client api of thrift service. 154 | #' 155 | #' @docType class 156 | #' @importFrom R6 R6Class 157 | #' @format An \code{\link{R6Class}} generator object 158 | #' 159 | #' @export 160 | TClient <- R6Class("TClient", 161 | lock_objects = FALSE, 162 | public = list( 163 | initialize = function(service, iprot, oprot=NULL) { 164 | private$service <- service 165 | private$iprot <- iprot 166 | private$oprot <- iprot 167 | if (!is.null(oprot)) { 168 | private$oprot <- oprot 169 | } 170 | private$seqid <- 0 171 | 172 | for (api in private$service$thrift_services) { 173 | func <- private$getFunc(api) 174 | 175 | args <- alist() 176 | args_thrift <- private$service[[paste(api, "args", sep = "_")]] 177 | args_spec <- args_thrift$thrift_spec 178 | args_default <- args_thrift$default_spec 179 | for(s in names(args_spec)) { 180 | arg_name <- args_spec[[s]][[2]] 181 | default <- NA 182 | for (d in args_default) { 183 | if (d[[1]] == arg_name) default <- d[[2]] 184 | } 185 | args[[arg_name]] <- default 186 | } 187 | formals(func) <- args 188 | 189 | self$add_function(api, func) 190 | } 191 | }, 192 | add_function = function(name, meth) { 193 | self[[name]] <- meth 194 | } 195 | ), 196 | private = list( 197 | service = NA, 198 | iprot = NA, 199 | oprot = NA, 200 | seqid = NA, 201 | getFunc = function(v_api) { 202 | api <- v_api 203 | result_cls <- private$service[[paste(api, "result", sep = "_")]]$new() 204 | function() { 205 | kwargs <- as.list(environment()) 206 | private$send(api, kwargs) 207 | # wait result only if non-oneway 208 | if (!result_cls$oneway) { 209 | return(private$recv(api)) 210 | } 211 | } 212 | }, 213 | send = function(api, kwargs) { 214 | private$oprot$write_message_begin(api, TMessageType$CALL, private$seqid) 215 | args <- private$service[[paste(api, "args", sep = "_")]]$new() 216 | for(arg_name in names(kwargs)) { 217 | args[[arg_name]] <- kwargs[[arg_name]] 218 | } 219 | args$write(private$oprot) 220 | private$oprot$write_message_end() 221 | private$oprot$trans$flush() 222 | }, 223 | recv = function(api) { 224 | fname_mtype_rseqid <- private$iprot$read_message_begin() 225 | fname <- fname_mtype_rseqid[[1]] 226 | mtype <- fname_mtype_rseqid[[2]] 227 | rseqid <- fname_mtype_rseqid[[3]] 228 | if (mtype == TMessageType$EXCEPTION) { 229 | x <- TApplicationException() 230 | x$read(private$iprot) 231 | private$iprot$read_message_end() 232 | stop(x) 233 | } 234 | result <- private$service[[paste(api, "result", sep = "_")]]$new() 235 | result$read(private$iprot) 236 | private$iprot$read_message_end() 237 | 238 | if (typeof(result$success) == "list" || 239 | typeof(result$success) == "environment" || 240 | (!is.null(result$success) && !is.na(result$success))) { 241 | return(result$success) 242 | } 243 | 244 | # void api without throws 245 | if (length(names(result$thrift_spec)) == 0) { 246 | return 247 | } 248 | 249 | # check throws 250 | # for k, v in result.__dict__.items(): 251 | # if k != "success" and v: 252 | # raise v 253 | 254 | # no throws & not void api 255 | if (!is.null(result$success)) { 256 | stop(paste("[TApplicationException]", TApplicationException$MISSING_RESULT)) 257 | } 258 | } 259 | ) 260 | ) 261 | 262 | # TProcessor 263 | # 264 | # Base class for procsessor, which works on two streams. 265 | TProcessor <- R6Class("TProcessor", 266 | public = list( 267 | initialize = function(service, handler) { 268 | private$service <- service 269 | private$handler <- handler 270 | }, 271 | process_in = function(iprot) { 272 | api_type_seqid <- iprot$read_message_begin() 273 | api <- api_type_seqid[[1]] 274 | type <- api_type_seqid[[2]] 275 | seqid <- api_type_seqid[[3]] 276 | if (!(api %in% private$service$thrift_services)) { 277 | iprot$skip(TType$STRUCT) 278 | iprot$read_message_end() 279 | return(list(api, seqid, TApplicationException$new(TApplicationException$UNKNOWN_METHOD), None)) # noqa 280 | } 281 | 282 | args <- private$service[[paste(api, "args", sep = "_")]]$new() 283 | args$read(iprot) 284 | iprot$read_message_end() 285 | result <- private$service[[paste(api, "result", sep = "_")]]$new() 286 | 287 | # convert kwargs to args 288 | api_args <- list() 289 | for (k in names(args$thrift_spec)) { 290 | api_args[[length(api_args) + 1]] <- args$thrift_spec[[k]][[2]] 291 | } 292 | 293 | call <- function() { 294 | f <- private$handler[[api]] 295 | as <- list() 296 | for (k in api_args) { 297 | as[[length(as) + 1]] <- args[[k]] 298 | } 299 | names(as) <- api_args 300 | return(do.call(f, as)) 301 | } 302 | 303 | return(list(api, seqid, result, call)) 304 | }, 305 | send_exception = function(oprot, api, exc, seqid) { 306 | oprot$write_message_begin(api, TMessageType$EXCEPTION, seqid) 307 | exc$write(oprot) 308 | oprot$write_message_end() 309 | oprot$trans.flush() 310 | }, 311 | send_result = function(oprot, api, result, seqid) { 312 | oprot$write_message_begin(api, TMessageType$REPLY, seqid) 313 | result$write(oprot) 314 | oprot$write_message_end() 315 | oprot$trans$flush() 316 | }, 317 | handle_exception = function(e, result) { 318 | should_raise <- FALSE 319 | for (k in result$thrift_spec) { 320 | should_raise <- TRUE 321 | if (result$thrift_spec[[k]][[2]] == "success") next 322 | 323 | na_exc_name_exc_cls_na = result$thrift_spec[[k]] 324 | # if isinstance(e, exc_cls): 325 | # setattr(result, exc_name, e) 326 | # break 327 | } 328 | if (should_raise) stop() 329 | }, 330 | process = function(iprot, oprot) { 331 | api_seqid_result_call <- self$process_in(iprot) 332 | api <- api_seqid_result_call[[1]] 333 | seqid <- api_seqid_result_call[[2]] 334 | result <- api_seqid_result_call[[3]] 335 | call <- api_seqid_result_call[[4]] 336 | 337 | if (class(result)[[1]] == "TApplicationException") { 338 | return(self$send_exception(oprot, api, result, seqid)) 339 | } 340 | 341 | tryCatch({ 342 | result$success <- call() 343 | }, error = function(e) { 344 | # raise if api don't have throws 345 | self$handle_exception(e, result) 346 | }) 347 | 348 | if (!result$oneway) { 349 | self$send_result(oprot, api, result, seqid) 350 | } 351 | } 352 | ), 353 | private = list( 354 | service = NA, 355 | handler = NA 356 | ) 357 | ) 358 | 359 | TException <- R6Class("TException", 360 | inherit = TPayload, 361 | public = list( 362 | ) 363 | ) 364 | 365 | TApplicationException <- R6Class("TApplicationException", 366 | inherit = TException, 367 | public = list( 368 | ) 369 | ) 370 | 371 | TApplicationException$UNKNOWN <- 0 372 | TApplicationException$UNKNOWN_METHOD <- 1 373 | TApplicationException$INVALID_MESSAGE_TYPE <- 2 374 | TApplicationException$WRONG_METHOD_NAME <- 3 375 | TApplicationException$BAD_SEQUENCE_ID <- 4 376 | TApplicationException$MISSING_RESULT <- 5 377 | TApplicationException$INTERNAL_ERROR <- 6 378 | TApplicationException$PROTOCOL_ERROR <- 7 379 | -------------------------------------------------------------------------------- /R/transport.R: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2016 System in Cloud - Marek Jagielski 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | 24 | 25 | 26 | readall = function(read_fn, sz) { 27 | buff <- raw() 28 | have <- 0 29 | while (have < sz) { 30 | chunk <- read_fn(sz - have) 31 | have <- have + length(chunk) 32 | buff <- c(buff, chunk) 33 | 34 | if (length(chunk) == 0) { 35 | stop(paste("[TTransportException]", TTransportException$END_OF_FILE, 36 | "End of file reading from transport")) 37 | 38 | } 39 | } 40 | 41 | return(buff) 42 | } 43 | 44 | 45 | # Base class for Thrift transport layer. 46 | TTransportBase <- R6Class("TTransportBase", 47 | public = list( 48 | read = function(sz) { 49 | return(readall(private$read_, sz)) 50 | } 51 | ), 52 | private = list( 53 | read_ = function(sz) { 54 | stop("NotImplementedError") 55 | } 56 | ) 57 | ) 58 | 59 | TTransportException <- new.env(hash=TRUE) 60 | TTransportException$UNKNOWN <- 0 61 | TTransportException$NOT_OPEN <- 1 62 | TTransportException$ALREADY_OPEN <- 2 63 | TTransportException$TIMED_OUT <- 3 64 | TTransportException$END_OF_FILE <- 4 -------------------------------------------------------------------------------- /R/transport_buffered.R: -------------------------------------------------------------------------------- 1 | 2 | DEFAULT_BUFFER <- 4096 3 | 4 | #' TBufferedTransport 5 | #' 6 | #' Class that wraps another transport and buffers its I/O. 7 | #' 8 | #' @docType class 9 | #' @importFrom R6 R6Class 10 | #' @format An \code{\link{R6Class}} generator object 11 | #' 12 | #' @export 13 | TBufferedTransport <- R6Class("TBufferedTransport", 14 | inherit = TTransportBase, 15 | public = list( 16 | initialize = function(trans, buf_size = DEFAULT_BUFFER) { 17 | private$trans <- trans 18 | private$wbuf <- TMemoryBuffer$new() 19 | private$rbuf <- TMemoryBuffer$new() 20 | private$buf_size <- buf_size 21 | }, 22 | is_open = function() { 23 | return(private$trans$is_open()) 24 | }, 25 | open = function() { 26 | return(private$trans$open()) 27 | }, 28 | close = function() { 29 | return(private$trans$close()) 30 | }, 31 | read = function(sz) { 32 | private$rbuf <- TMemoryBuffer$new() 33 | private$rbuf$setvalue(private$trans$read(sz)) 34 | private$rbuf$read(sz) 35 | }, 36 | write = function(buf) { 37 | private$wbuf$write(buf) 38 | }, 39 | flush = function() { 40 | out <- private$wbuf$getvalue() 41 | # reset wbuf before write/flush to preserve state on underlying failure 42 | private$wbuf <- TMemoryBuffer$new() 43 | private$trans$write(out) 44 | private$trans$flush() 45 | }, 46 | getvalue = function() { 47 | return(self$trans$getvalue()) 48 | } 49 | ), 50 | private = list( 51 | trans = NA, 52 | wbuf = NA, 53 | rbuf = NA, 54 | buf_size = NA, 55 | read_ = function(sz) { 56 | res <- head(self$buffer, sz) 57 | self$buffer <- tail(self$buffer, -sz) 58 | self$pos <- self$pos + length(res) 59 | return(res) 60 | } 61 | ) 62 | ) 63 | 64 | #' TBufferedTransportFactory 65 | #' 66 | #' TBufferedTransportFactory generates TBufferedTransport. 67 | #' 68 | #' @docType class 69 | #' @importFrom R6 R6Class 70 | #' @format An \code{\link{R6Class}} generator object 71 | #' 72 | #' @export 73 | TBufferedTransportFactory <- R6Class("TBufferedTransportFactory", 74 | public = list( 75 | get_transport = function(trans) { 76 | return(TBufferedTransport$new(trans)) 77 | } 78 | ) 79 | ) 80 | -------------------------------------------------------------------------------- /R/transport_memory.R: -------------------------------------------------------------------------------- 1 | 2 | #' TMemoryBuffer 3 | #' 4 | #' Wraps a raw array as a TTransport. 5 | #' 6 | #' @docType class 7 | #' @importFrom R6 R6Class 8 | #' @format An \code{\link{R6Class}} generator object 9 | #' 10 | #' @export 11 | TMemoryBuffer <- R6Class("TMemoryBuffer", 12 | inherit = TTransportBase, 13 | public = list( 14 | buffer = NA, 15 | pos = NA, 16 | # value -- a value as the initial value in the buffer object. 17 | # If value is set, the transport can be read first. 18 | initialize = function(value = NULL) { 19 | if (!is.null(value)) { 20 | self$buffer <- value 21 | } else { 22 | self$buffer <- raw() 23 | } 24 | self$pos <- 0 25 | }, 26 | is_open = function() { 27 | return(!self$buffer.closed) 28 | }, 29 | open = function() { 30 | }, 31 | close = function() { 32 | buffer.close() 33 | }, 34 | read = function(sz) { 35 | return(private$read_(sz)) 36 | }, 37 | write = function(buf) { 38 | self$buffer <- c(self$buffer, buf) 39 | }, 40 | flush = function() { 41 | }, 42 | getvalue = function() { 43 | return(self$buffer) 44 | }, 45 | setvalue = function(value) { 46 | self$buffer <- value 47 | self$pos <- 0 48 | } 49 | ), 50 | private = list( 51 | read_ = function(sz) { 52 | res <- head(self$buffer, sz) 53 | self$buffer <- tail(self$buffer, -sz) 54 | self$pos <- self$pos + length(res) 55 | return(res) 56 | } 57 | ) 58 | ) 59 | -------------------------------------------------------------------------------- /R/transport_socket.R: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2016 System in Cloud - Marek Jagielski 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | #' TSocket 24 | #' 25 | #' Socket implementation for client side. 26 | #' 27 | #' @docType class 28 | #' @importFrom R6 R6Class 29 | #' @format An \code{\link{R6Class}} generator object 30 | #' 31 | #' @export 32 | TSocket <- R6Class("TSocket", 33 | public = list( 34 | host = NA, 35 | port = NA, 36 | server = NA, 37 | sock = NA, 38 | initialize = function( 39 | host, 40 | port, 41 | server = FALSE) { 42 | self$host <- host 43 | self$port <- port 44 | self$server <- server 45 | }, 46 | is_open = function() { 47 | !is.na(self$sock) && isOpen(self$sock) 48 | }, 49 | open = function() { 50 | private$init_sock() 51 | }, 52 | read = function(sz) { 53 | return(readBin(self$sock, raw(), sz)) 54 | }, 55 | write = function(buff) { 56 | writeBin(buff, self$sock) 57 | }, 58 | flush = function() { 59 | }, 60 | close = function() { 61 | if (is.na(self$sock)) { 62 | return() 63 | } 64 | close(self$sock) 65 | self$sock <- NA 66 | } 67 | ), 68 | private = list( 69 | init_sock = function() { 70 | self$sock <- socketConnection( 71 | host = self$host, 72 | port = self$port, 73 | blocking = TRUE, 74 | server = self$server, 75 | open = "r+b") 76 | } 77 | ) 78 | ) 79 | 80 | 81 | #' TServerSocket 82 | #' 83 | #' Socket implementation for server side. 84 | #' 85 | #' @docType class 86 | #' @importFrom R6 R6Class 87 | #' @format An \code{\link{R6Class}} generator object 88 | #' 89 | #' @export 90 | TServerSocket <- R6Class("TServerSocket", 91 | public = list( 92 | host = NA, 93 | port = NA, 94 | sock = NA, 95 | initialize = function( 96 | host = NA, 97 | port = NA) { 98 | self$host <- host 99 | self$port <- port 100 | }, 101 | listen = function() { 102 | self$sock <- TSocket$new(self$host, self$port, TRUE) 103 | }, 104 | accept = function() { 105 | self$sock$open() 106 | return(self$sock) 107 | } 108 | ) 109 | ) 110 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2016 System in Cloud - Marek Jagielski 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | #' hexlify 24 | #' 25 | #' String representation of raw array 26 | #' 27 | #' @param byte_array raw array 28 | #' @param delimeter separation character 29 | #' 30 | #' @return string 31 | #' 32 | #' @export 33 | hexlify = function(byte_array, delimeter = ' ') { 34 | as.character(paste(byte_array, collapse = delimeter)) 35 | } 36 | 37 | #' to_proper_struct 38 | #' 39 | #' Help method for tests. It changes predefined structure to parsed thrift 40 | #' instead of parsing file. 41 | #' 42 | #' @param thrift_spec_list raw array 43 | #' @param default_spec separation character 44 | #' 45 | #' @return R6 class 46 | #' 47 | #' @export 48 | to_proper_struct <- function(thrift_spec_list, default_spec) { 49 | thrift_spec <- new.env(hash = TRUE) 50 | for (input in thrift_spec_list) { 51 | thrift_spec[[input[[1]]]] <- input[[2]] 52 | } 53 | 54 | TItem <- R6::R6Class( 55 | "TItem", 56 | inherit = thriftr::TPayload, 57 | lock_objects = FALSE, 58 | public = list( 59 | ) 60 | ) 61 | 62 | TItem$set("public", 'thrift_spec', thrift_spec) 63 | TItem$set("public", 'default_spec', default_spec) 64 | 65 | gen_init( 66 | TItem, 67 | thrift_spec, 68 | default_spec 69 | ) 70 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/systemincloud/thriftr.svg?branch=master)](https://travis-ci.org/systemincloud/thriftr) 2 | [![CRAN\_Status\_Badge](https://www.r-pkg.org/badges/version/thriftr)](https://cran.r-project.org/package=thriftr) 3 | [![](https://cranlogs.r-pkg.org/badges/thriftr)](https://cran.r-project.org/package=thriftr) 4 | [![codecov](https://codecov.io/gh/systemincloud/thriftr/branch/master/graph/badge.svg)](https://codecov.io/gh/systemincloud/thriftr) 5 | [![PayPal donation](https://img.shields.io/badge/donation-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=UR288FRQUSYQE&item_name=Thriftr+-+R+Thrift¤cy_code=USD&source=url) 6 | 7 | Introduction 8 | ============ 9 | 10 | ThriftR is a pure R implementation of [Apache Thrift](http://thrift.apache.org) in R way. 11 | This project is a R clone of [ThriftPy](https://github.com/eleme/thriftpy). 12 | 13 | How to Use 14 | ========== 15 | 16 | library(thriftr) 17 | 18 | The examples directory contains several different examples. 19 | 20 | A simple example is found at the end of this document 21 | 22 | Resources 23 | ========= 24 | The GitHub page for ThriftR can be found at: 25 | 26 | https://github.com/systemincloud/thriftr 27 | 28 | 29 | Example 30 | ======= 31 | 32 | ThriftR make it super easy to write server/client code with thrift. Let's checkout this simple pingpong service demo. 33 | 34 | We need a 'pingpong.thrift' file: 35 | 36 | ``` 37 | service PingPong { 38 | string ping(), 39 | } 40 | ``` 41 | 42 | Then we can make a server: 43 | 44 | 45 | ```R 46 | library(thriftr) 47 | 48 | pingpong_thrift = thriftr::t_load("pingpong.thrift", module_name="pingpong_thrift") 49 | 50 | Dispatcher <- R6::R6Class("Dispatcher", 51 | public = list( 52 | ping = function() { 53 | return('pong') 54 | } 55 | ) 56 | ) 57 | 58 | server = thriftr::make_server(pingpong_thrift$PingPong, Dispatcher$new(), '127.0.0.1', 6000) 59 | server$serve() 60 | ``` 61 | 62 | And a client: 63 | 64 | ```R 65 | library(thriftr) 66 | 67 | pingpong_thrift = thriftr::t_load("pingpong.thrift", module_name="pingpong_thrift") 68 | 69 | client = thriftr::make_client(pingpong_thrift$PingPong, "127.0.0.1", 6000) 70 | cut(client$ping()) 71 | ``` 72 | -------------------------------------------------------------------------------- /demo/00Index: -------------------------------------------------------------------------------- 1 | calc_client An example of simple calculator client 2 | calc_server An example of simple calculator server 3 | ping_client An example of simple client 4 | ping_server An example of simple server 5 | sleep_client An example of client with oneway method 6 | sleep_server An example of server with oneway method 7 | -------------------------------------------------------------------------------- /demo/calc/calc.thrift: -------------------------------------------------------------------------------- 1 | service Calculator { 2 | i32 add(1:i32 a, 2:i32 b); 3 | i32 sub(1:i32 a, 2:i32 b); 4 | i32 mult(1:i32 a, 2:i32 b); 5 | i32 div(1:i32 a, 2:i32 b); 6 | } 7 | -------------------------------------------------------------------------------- /demo/calc/calc_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import thriftpy 4 | 5 | from thriftpy.protocol import TCyBinaryProtocolFactory 6 | from thriftpy.transport import TCyBufferedTransportFactory 7 | from thriftpy.rpc import client_context 8 | 9 | calc_thrift = thriftpy.load("calc.thrift", module_name="calc_thrift") 10 | 11 | 12 | def main(): 13 | with client_context(calc_thrift.Calculator, '127.0.0.1', 6000, 14 | proto_factory=TCyBinaryProtocolFactory(), 15 | trans_factory=TCyBufferedTransportFactory()) as cal: 16 | a = cal.mult(5, 2) 17 | b = cal.sub(7, 3) 18 | c = cal.sub(6, 4) 19 | d = cal.mult(b, 10) 20 | e = cal.add(a, d) 21 | f = cal.div(e, c) 22 | print(f) 23 | 24 | 25 | if __name__ == '__main__': 26 | main() 27 | -------------------------------------------------------------------------------- /demo/calc/calc_server.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import thriftpy 4 | 5 | from thriftpy.protocol import TCyBinaryProtocolFactory 6 | from thriftpy.transport import TCyBufferedTransportFactory 7 | from thriftpy.rpc import make_server 8 | 9 | calc_thrift = thriftpy.load("calc.thrift", module_name="calc_thrift") 10 | 11 | 12 | class Dispatcher(object): 13 | def add(self, a, b): 14 | print("add -> %s + %s" % (a, b)) 15 | return a + b 16 | 17 | def sub(self, a, b): 18 | print("sub -> %s - %s" % (a, b)) 19 | return a - b 20 | 21 | def mult(self, a, b): 22 | print("mult -> %s * %s" % (a, b)) 23 | return a * b 24 | 25 | def div(self, a, b): 26 | print("div -> %s / %s" % (a, b)) 27 | return a // b 28 | 29 | 30 | def main(): 31 | server = make_server(calc_thrift.Calculator, Dispatcher(), 32 | '127.0.0.1', 6000, 33 | proto_factory=TCyBinaryProtocolFactory(), 34 | trans_factory=TCyBufferedTransportFactory()) 35 | print("serving...") 36 | server.serve() 37 | 38 | 39 | if __name__ == '__main__': 40 | main() 41 | -------------------------------------------------------------------------------- /demo/calc_client.R: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env Rscript 2 | 3 | calc_thrift <- thriftr::t_load("calc/calc.thrift", module_name="calc_thrift") 4 | 5 | cal <- thriftr::make_client( 6 | calc_thrift$Calculator, 7 | "127.0.0.1", 8 | 6000) 9 | 10 | a <- cal$mult(5, 2) 11 | b <- cal$sub(7, 3) 12 | c <- cal$sub(6, 4) 13 | d <- cal$mult(b, 10) 14 | e <- cal$add(a, d) 15 | f <- cal$div(e, c) 16 | print(f) 17 | -------------------------------------------------------------------------------- /demo/calc_server.R: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env Rscript 2 | 3 | calc_thrift <- thriftr::t_load("calc/calc.thrift", module_name="calc_thrift") 4 | 5 | Dispatcher <- R6::R6Class("Dispatcher", 6 | public = list( 7 | add = function(a, b) { 8 | print(sprintf("add -> %s + %s", a, b)) 9 | return(a + b) 10 | }, 11 | sub = function(a, b) { 12 | print(sprintf("sub -> %s - %s", a, b)) 13 | return(a - b) 14 | }, 15 | mult = function(a, b) { 16 | print(sprintf("mult -> %s * %s", a, b)) 17 | return(a * b) 18 | }, 19 | div = function(a, b) { 20 | print(sprintf("div -> %s / %s", a, b)) 21 | return(a / b) 22 | } 23 | ) 24 | ) 25 | 26 | server <- thriftr::make_server( 27 | calc_thrift$Calculator, 28 | Dispatcher$new(), 29 | "127.0.0.1", 30 | 6000) 31 | 32 | print("serving...") 33 | 34 | server$serve() 35 | -------------------------------------------------------------------------------- /demo/oneway/sleep.thrift: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple example to show how to use `oneway` with thriftpy. 3 | */ 4 | service Sleep { 5 | /** 6 | * Tell the server to sleep! 7 | */ 8 | oneway void sleep(1: i32 seconds) 9 | } 10 | -------------------------------------------------------------------------------- /demo/oneway/sleep_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import thriftpy 4 | 5 | from thriftpy.rpc import make_client 6 | 7 | sleep_thrift = thriftpy.load("sleep.thrift", module_name="sleep_thrift") 8 | 9 | 10 | def main(): 11 | client = make_client(sleep_thrift.Sleep, '127.0.0.1', 6000) 12 | # sleep multiple times, but the client won't wait any more 13 | client.sleep(1) 14 | client.sleep(2) 15 | client.sleep(3) 16 | client.sleep(4) 17 | 18 | 19 | if __name__ == '__main__': 20 | main() 21 | -------------------------------------------------------------------------------- /demo/oneway/sleep_server.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import time 4 | import thriftpy 5 | 6 | from thriftpy.rpc import make_server 7 | 8 | sleep_thrift = thriftpy.load("sleep.thrift", module_name="sleep_thrift") 9 | 10 | 11 | class Dispatcher(object): 12 | def sleep(self, seconds): 13 | print("I'm going to sleep %d seconds" % seconds) 14 | time.sleep(seconds) 15 | print("Sleep over!") 16 | 17 | 18 | def main(): 19 | server = make_server(sleep_thrift.Sleep, Dispatcher(), 20 | '127.0.0.1', 6000) 21 | print("serving...") 22 | server.serve() 23 | 24 | 25 | if __name__ == '__main__': 26 | main() 27 | -------------------------------------------------------------------------------- /demo/ping/ping_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import thriftpy 4 | 5 | from thriftpy.rpc import client_context 6 | 7 | pp_thrift = thriftpy.load("pingpong.thrift", module_name="pp_thrift") 8 | 9 | 10 | def main(): 11 | with client_context(pp_thrift.PingService, '127.0.0.1', 6000) as c: 12 | pong = c.ping() 13 | print(pong) 14 | 15 | 16 | if __name__ == '__main__': 17 | main() 18 | -------------------------------------------------------------------------------- /demo/ping/ping_server.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import thriftpy 4 | 5 | from thriftpy.rpc import make_server 6 | 7 | pp_thrift = thriftpy.load("pingpong.thrift", module_name="pp_thrift") 8 | 9 | 10 | class Dispatcher(object): 11 | def ping(self): 12 | print("ping pong!") 13 | return 'pong' 14 | 15 | 16 | def main(): 17 | server = make_server(pp_thrift.PingService, Dispatcher(), 18 | '127.0.0.1', 6000) 19 | print("serving...") 20 | server.serve() 21 | 22 | 23 | if __name__ == '__main__': 24 | main() 25 | -------------------------------------------------------------------------------- /demo/ping/pingpong.thrift: -------------------------------------------------------------------------------- 1 | # ping service demo 2 | service PingService { 3 | /* 4 | * Sexy c style comment 5 | */ 6 | string ping(), 7 | } 8 | -------------------------------------------------------------------------------- /demo/ping_client.R: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env Rscript 2 | 3 | pp_thrift <- thriftr::t_load("ping/pingpong.thrift", module_name="pp_thrift") 4 | 5 | c <- thriftr::make_client( 6 | pp_thrift$PingService, 7 | "127.0.0.1", 8 | 6000) 9 | 10 | pong <- c$ping() 11 | print(pong) 12 | -------------------------------------------------------------------------------- /demo/ping_server.R: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env Rscript 2 | 3 | pp_thrift <- thriftr::t_load("ping/pingpong.thrift", module_name="pp_thrift") 4 | 5 | Dispatcher <- R6::R6Class("Dispatcher", 6 | public = list( 7 | ping = function() { 8 | print("ping pong!") 9 | return("pong") 10 | } 11 | ) 12 | ) 13 | 14 | server <- thriftr::make_server( 15 | pp_thrift$PingService, 16 | Dispatcher$new(), 17 | "127.0.0.1", 18 | 6000) 19 | 20 | print("serving...") 21 | 22 | server$serve() 23 | -------------------------------------------------------------------------------- /demo/sleep_client.R: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env Rscript 2 | 3 | sleep_thrift <- thriftr::t_load("oneway/sleep.thrift", module_name="sleep_thrift") 4 | 5 | client <- thriftr::make_client( 6 | sleep_thrift$Sleep, 7 | "127.0.0.1", 8 | 6000) 9 | # sleep multiple times, but the client won't wait any more 10 | client$sleep(1) 11 | client$sleep(2) 12 | client$sleep(3) 13 | client$sleep(4) 14 | -------------------------------------------------------------------------------- /demo/sleep_server.R: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env Rscript 2 | 3 | sleep_thrift <- thriftr::t_load("oneway/sleep.thrift", module_name="sleep_thrift") 4 | 5 | Dispatcher <- R6::R6Class("Dispatcher", 6 | public = list( 7 | sleep = function(seconds) { 8 | print(sprintf("I'm going to sleep %d seconds", seconds)) 9 | Sys.sleep(seconds) 10 | print("Sleep over!") 11 | } 12 | ) 13 | ) 14 | 15 | server <- thriftr::make_server( 16 | sleep_thrift$Sleep, 17 | Dispatcher$new(), 18 | "127.0.0.1", 19 | 6000) 20 | 21 | print("serving...") 22 | 23 | server$serve() 24 | -------------------------------------------------------------------------------- /man/TBinaryProtocol.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/protocol_binary.R 3 | \docType{class} 4 | \name{TBinaryProtocol} 5 | \alias{TBinaryProtocol} 6 | \title{TBinaryProtocol} 7 | \format{An \code{\link[R6]{R6Class}} generator object} 8 | \usage{ 9 | TBinaryProtocol 10 | } 11 | \description{ 12 | Binary implementation of the Thrift protocol driver. 13 | } 14 | \keyword{datasets} 15 | -------------------------------------------------------------------------------- /man/TBinaryProtocolFactory.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/protocol_binary.R 3 | \docType{class} 4 | \name{TBinaryProtocolFactory} 5 | \alias{TBinaryProtocolFactory} 6 | \title{TBinaryProtocolFactory} 7 | \format{An \code{\link[R6]{R6Class}} generator object} 8 | \usage{ 9 | TBinaryProtocolFactory 10 | } 11 | \description{ 12 | TBinaryProtocolFactory generates TBinaryProtocol driver. 13 | } 14 | \keyword{datasets} 15 | -------------------------------------------------------------------------------- /man/TBufferedTransport.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/transport_buffered.R 3 | \docType{class} 4 | \name{TBufferedTransport} 5 | \alias{TBufferedTransport} 6 | \title{TBufferedTransport} 7 | \format{An \code{\link[R6]{R6Class}} generator object} 8 | \usage{ 9 | TBufferedTransport 10 | } 11 | \description{ 12 | Class that wraps another transport and buffers its I/O. 13 | } 14 | \keyword{datasets} 15 | -------------------------------------------------------------------------------- /man/TBufferedTransportFactory.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/transport_buffered.R 3 | \docType{class} 4 | \name{TBufferedTransportFactory} 5 | \alias{TBufferedTransportFactory} 6 | \title{TBufferedTransportFactory} 7 | \format{An \code{\link[R6]{R6Class}} generator object} 8 | \usage{ 9 | TBufferedTransportFactory 10 | } 11 | \description{ 12 | TBufferedTransportFactory generates TBufferedTransport. 13 | } 14 | \keyword{datasets} 15 | -------------------------------------------------------------------------------- /man/TClient.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/thrift.R 3 | \docType{class} 4 | \name{TClient} 5 | \alias{TClient} 6 | \title{TClient} 7 | \format{An \code{\link[R6]{R6Class}} generator object} 8 | \usage{ 9 | TClient 10 | } 11 | \description{ 12 | TClient implements client api of thrift service. 13 | } 14 | \keyword{datasets} 15 | -------------------------------------------------------------------------------- /man/TMemoryBuffer.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/transport_memory.R 3 | \docType{class} 4 | \name{TMemoryBuffer} 5 | \alias{TMemoryBuffer} 6 | \title{TMemoryBuffer} 7 | \format{An \code{\link[R6]{R6Class}} generator object} 8 | \usage{ 9 | TMemoryBuffer 10 | } 11 | \description{ 12 | Wraps a raw array as a TTransport. 13 | } 14 | \keyword{datasets} 15 | -------------------------------------------------------------------------------- /man/TPayload.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/thrift.R 3 | \docType{class} 4 | \name{TPayload} 5 | \alias{TPayload} 6 | \title{TPayload} 7 | \format{An \code{\link[R6]{R6Class}} generator object} 8 | \usage{ 9 | TPayload 10 | } 11 | \description{ 12 | Base class for all complex types of api. 13 | } 14 | \keyword{datasets} 15 | -------------------------------------------------------------------------------- /man/TServerSocket.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/transport_socket.R 3 | \docType{class} 4 | \name{TServerSocket} 5 | \alias{TServerSocket} 6 | \title{TServerSocket} 7 | \format{An \code{\link[R6]{R6Class}} generator object} 8 | \usage{ 9 | TServerSocket 10 | } 11 | \description{ 12 | Socket implementation for server side. 13 | } 14 | \keyword{datasets} 15 | -------------------------------------------------------------------------------- /man/TSocket.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/transport_socket.R 3 | \docType{class} 4 | \name{TSocket} 5 | \alias{TSocket} 6 | \title{TSocket} 7 | \format{An \code{\link[R6]{R6Class}} generator object} 8 | \usage{ 9 | TSocket 10 | } 11 | \description{ 12 | Socket implementation for client side. 13 | } 14 | \keyword{datasets} 15 | -------------------------------------------------------------------------------- /man/TType.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/thrift.R 3 | \docType{data} 4 | \name{TType} 5 | \alias{TType} 6 | \title{TType} 7 | \format{An object of class \code{environment} of length 18.} 8 | \usage{ 9 | TType 10 | } 11 | \description{ 12 | Identificator of value type. 13 | } 14 | \keyword{datasets} 15 | -------------------------------------------------------------------------------- /man/binary_read_val.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/protocol_binary.R 3 | \name{binary_read_val} 4 | \alias{binary_read_val} 5 | \title{Binary protocol: read value from binary buffer} 6 | \usage{ 7 | binary_read_val(inbuf, ttype, spec = NA, decode_response = TRUE) 8 | } 9 | \arguments{ 10 | \item{inbuf}{binary buffor} 11 | 12 | \item{ttype}{type of value} 13 | 14 | \item{spec}{specification of value} 15 | 16 | \item{decode_response}{for string decode binary as chars} 17 | } 18 | \value{ 19 | value of type ttype 20 | } 21 | \description{ 22 | Binary protocol: read value from binary buffer 23 | } 24 | -------------------------------------------------------------------------------- /man/binary_write_val.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/protocol_binary.R 3 | \name{binary_write_val} 4 | \alias{binary_write_val} 5 | \title{Binary protocol: write value to binary buffer} 6 | \usage{ 7 | binary_write_val(outbuf, ttype, val, spec = NA) 8 | } 9 | \arguments{ 10 | \item{outbuf}{binary buffor} 11 | 12 | \item{ttype}{type of value} 13 | 14 | \item{val}{value to write} 15 | 16 | \item{spec}{specification of value} 17 | } 18 | \description{ 19 | Binary protocol: write value to binary buffer 20 | } 21 | -------------------------------------------------------------------------------- /man/hexlify.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{hexlify} 4 | \alias{hexlify} 5 | \title{hexlify} 6 | \usage{ 7 | hexlify(byte_array, delimeter = " ") 8 | } 9 | \arguments{ 10 | \item{byte_array}{raw array} 11 | 12 | \item{delimeter}{separation character} 13 | } 14 | \value{ 15 | string 16 | } 17 | \description{ 18 | String representation of raw array 19 | } 20 | -------------------------------------------------------------------------------- /man/make_client.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rpc.R 3 | \name{make_client} 4 | \alias{make_client} 5 | \title{Create client side thrift API} 6 | \usage{ 7 | make_client(service, host = "localhost", port = 9090, 8 | proto_factory = TBinaryProtocolFactory$new(), 9 | trans_factory = TBufferedTransportFactory$new()) 10 | } 11 | \arguments{ 12 | \item{service}{parsed service} 13 | 14 | \item{host}{server host} 15 | 16 | \item{port}{server tcp port} 17 | 18 | \item{proto_factory}{factory that generates protocol implementation} 19 | 20 | \item{trans_factory}{factory that generates transport implementation} 21 | } 22 | \description{ 23 | Create client side thrift API 24 | } 25 | \examples{ 26 | \dontrun{ 27 | # File calc.thrift content: 28 | # service Calculator { 29 | # i32 add(1:i32 a, 2:i32 b); 30 | # i32 sub(1:i32 a, 2:i32 b); 31 | # i32 mult(1:i32 a, 2:i32 b); 32 | # i32 div(1:i32 a, 2:i32 b); 33 | # } 34 | # 35 | 36 | calc_thrift <- thriftr::t_load("calc.thrift", module_name="calc_thrift") 37 | 38 | cal <- thriftr::make_client( 39 | calc_thrift$Calculator, 40 | "127.0.0.1", 41 | 6000) 42 | 43 | a <- cal$mult(5, 2) 44 | b <- cal$sub(7, 3) 45 | c <- cal$sub(6, 4) 46 | d <- cal$mult(b, 10) 47 | e <- cal$add(a, d) 48 | f <- cal$div(e, c) 49 | print(f) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /man/make_server.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rpc.R 3 | \name{make_server} 4 | \alias{make_server} 5 | \title{Create server side thrift API} 6 | \usage{ 7 | make_server(service, handler, host = "localhost", port = 9090, 8 | proto_factory = TBinaryProtocolFactory$new(), 9 | trans_factory = TBufferedTransportFactory$new()) 10 | } 11 | \arguments{ 12 | \item{service}{parsed service} 13 | 14 | \item{handler}{R6 class implementing service} 15 | 16 | \item{host}{server host} 17 | 18 | \item{port}{port server tcp port} 19 | 20 | \item{proto_factory}{factory that generates protocol implementation} 21 | 22 | \item{trans_factory}{factory that generates transport implementation} 23 | } 24 | \description{ 25 | Create server side thrift API 26 | } 27 | \examples{ 28 | \dontrun{ 29 | # File calc.thrift content: 30 | # service Calculator { 31 | # i32 add(1:i32 a, 2:i32 b); 32 | # i32 sub(1:i32 a, 2:i32 b); 33 | # i32 mult(1:i32 a, 2:i32 b); 34 | # i32 div(1:i32 a, 2:i32 b); 35 | # } 36 | # 37 | 38 | calc_thrift <- thriftr::t_load("calc.thrift", module_name="calc_thrift") 39 | 40 | Dispatcher <- R6::R6Class("Dispatcher", 41 | public = list( 42 | add = function(a, b) { 43 | print(sprintf("add -> \%s + \%s", a, b)) 44 | return(a + b) 45 | }, 46 | sub = function(a, b) { 47 | print(sprintf("sub -> \%s - \%s", a, b)) 48 | return(a - b) 49 | }, 50 | mult = function(a, b) { 51 | print(sprintf("mult -> \%s * \%s", a, b)) 52 | return(a * b) 53 | }, 54 | div = function(a, b) { 55 | print(sprintf("div -> \%s / \%s", a, b)) 56 | return(a / b) 57 | } 58 | ) 59 | ) 60 | 61 | server <- thriftr::make_server( 62 | calc_thrift$Calculator, 63 | Dispatcher$new(), 64 | "127.0.0.1", 65 | 6000) 66 | 67 | print("serving...") 68 | 69 | server$serve() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /man/parse.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/parser.R 3 | \name{parse} 4 | \alias{parse} 5 | \title{Parse a single thrift file to R6 class instance} 6 | \usage{ 7 | parse(path, module_name = NA, include_dirs = NA, lexer = NA, 8 | parser = NA, enable_cache = TRUE) 9 | } 10 | \arguments{ 11 | \item{path}{file path to parse, should be a string ending with '.thrift'} 12 | 13 | \item{module_name}{the name for parsed module, the default is the basename 14 | without extension of `path`} 15 | 16 | \item{include_dirs}{directories to find thrift files while processing 17 | the `include` directive, by default: ['.']} 18 | 19 | \item{lexer}{rly lexer to use, if not provided, `parse` will use a new one} 20 | 21 | \item{parser}{rly parser to use, if not provided, `parse` will use a new one} 22 | 23 | \item{enable_cache}{if this is set to be `TRUE`, parsed module will be 24 | cached, this is enabled by default. If `module_name` 25 | is provided, use it as cache key, else use the `path`} 26 | } 27 | \value{ 28 | Thrift module 29 | } 30 | \description{ 31 | Parse a single thrift file to R6 class instance 32 | } 33 | -------------------------------------------------------------------------------- /man/parse_spec.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/thrift.R 3 | \name{parse_spec} 4 | \alias{parse_spec} 5 | \title{parse_spec} 6 | \usage{ 7 | parse_spec(ttype, spec = NA) 8 | } 9 | \arguments{ 10 | \item{ttype}{type} 11 | 12 | \item{spec}{specification} 13 | } 14 | \value{ 15 | string representation 16 | } 17 | \description{ 18 | String representation of specification 19 | } 20 | -------------------------------------------------------------------------------- /man/t_load.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/parser.R 3 | \name{t_load} 4 | \alias{t_load} 5 | \title{Load thrift file as a R6 instance.} 6 | \usage{ 7 | t_load(path, module_name = NA, include_dirs = NA) 8 | } 9 | \arguments{ 10 | \item{path}{file path to parse, should be a string ending with '.thrift'} 11 | 12 | \item{module_name}{the name for parsed module, the default is the basename 13 | without extension of `path`} 14 | 15 | \item{include_dirs}{directories to find thrift files while processing 16 | the `include` directive, by default: ['.']} 17 | } 18 | \value{ 19 | Thrift R6 class instance 20 | } 21 | \description{ 22 | The module loaded and objects inside may only be pickled if module_name 23 | was provided. 24 | } 25 | -------------------------------------------------------------------------------- /man/to_proper_struct.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{to_proper_struct} 4 | \alias{to_proper_struct} 5 | \title{to_proper_struct} 6 | \usage{ 7 | to_proper_struct(thrift_spec_list, default_spec) 8 | } 9 | \arguments{ 10 | \item{thrift_spec_list}{raw array} 11 | 12 | \item{default_spec}{separation character} 13 | } 14 | \value{ 15 | R6 class 16 | } 17 | \description{ 18 | Help method for tests. It changes predefined structure to parsed thrift 19 | instead of parsing file. 20 | } 21 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.4 23 | # 24 | # Optional ENV vars 25 | # ----------------- 26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source 27 | # MVNW_REPOURL - repo url base for downloading maven distribution 28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output 30 | # ---------------------------------------------------------------------------- 31 | 32 | set -euf 33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x 34 | 35 | # OS specific support. 36 | native_path() { printf %s\\n "$1"; } 37 | case "$(uname)" in 38 | CYGWIN* | MINGW*) 39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" 40 | native_path() { cygpath --path --windows "$1"; } 41 | ;; 42 | esac 43 | 44 | # set JAVACMD and JAVACCMD 45 | set_java_home() { 46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 47 | if [ -n "${JAVA_HOME-}" ]; then 48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 49 | # IBM's JDK on AIX uses strange locations for the executables 50 | JAVACMD="$JAVA_HOME/jre/sh/java" 51 | JAVACCMD="$JAVA_HOME/jre/sh/javac" 52 | else 53 | JAVACMD="$JAVA_HOME/bin/java" 54 | JAVACCMD="$JAVA_HOME/bin/javac" 55 | 56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then 57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 59 | return 1 60 | fi 61 | fi 62 | else 63 | JAVACMD="$( 64 | 'set' +e 65 | 'unset' -f command 2>/dev/null 66 | 'command' -v java 67 | )" || : 68 | JAVACCMD="$( 69 | 'set' +e 70 | 'unset' -f command 2>/dev/null 71 | 'command' -v javac 72 | )" || : 73 | 74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then 75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 76 | return 1 77 | fi 78 | fi 79 | } 80 | 81 | # hash string like Java String::hashCode 82 | hash_string() { 83 | str="${1:-}" h=0 84 | while [ -n "$str" ]; do 85 | char="${str%"${str#?}"}" 86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) 87 | str="${str#?}" 88 | done 89 | printf %x\\n $h 90 | } 91 | 92 | verbose() { :; } 93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } 94 | 95 | die() { 96 | printf %s\\n "$1" >&2 97 | exit 1 98 | } 99 | 100 | trim() { 101 | # MWRAPPER-139: 102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 103 | # Needed for removing poorly interpreted newline sequences when running in more 104 | # exotic environments such as mingw bash on Windows. 105 | printf "%s" "${1}" | tr -d '[:space:]' 106 | } 107 | 108 | scriptDir="$(dirname "$0")" 109 | scriptName="$(basename "$0")" 110 | 111 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties 112 | while IFS="=" read -r key value; do 113 | case "${key-}" in 114 | distributionUrl) distributionUrl=$(trim "${value-}") ;; 115 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; 116 | esac 117 | done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" 118 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 119 | 120 | case "${distributionUrl##*/}" in 121 | maven-mvnd-*bin.*) 122 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ 123 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in 124 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; 125 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; 126 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; 127 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;; 128 | *) 129 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 130 | distributionPlatform=linux-amd64 131 | ;; 132 | esac 133 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" 134 | ;; 135 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; 136 | *) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; 137 | esac 138 | 139 | # apply MVNW_REPOURL and calculate MAVEN_HOME 140 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 141 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" 142 | distributionUrlName="${distributionUrl##*/}" 143 | distributionUrlNameMain="${distributionUrlName%.*}" 144 | distributionUrlNameMain="${distributionUrlNameMain%-bin}" 145 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" 146 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" 147 | 148 | exec_maven() { 149 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : 150 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" 151 | } 152 | 153 | if [ -d "$MAVEN_HOME" ]; then 154 | verbose "found existing MAVEN_HOME at $MAVEN_HOME" 155 | exec_maven "$@" 156 | fi 157 | 158 | case "${distributionUrl-}" in 159 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; 160 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; 161 | esac 162 | 163 | # prepare tmp dir 164 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then 165 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } 166 | trap clean HUP INT TERM EXIT 167 | else 168 | die "cannot create temp dir" 169 | fi 170 | 171 | mkdir -p -- "${MAVEN_HOME%/*}" 172 | 173 | # Download and Install Apache Maven 174 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 175 | verbose "Downloading from: $distributionUrl" 176 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 177 | 178 | # select .zip or .tar.gz 179 | if ! command -v unzip >/dev/null; then 180 | distributionUrl="${distributionUrl%.zip}.tar.gz" 181 | distributionUrlName="${distributionUrl##*/}" 182 | fi 183 | 184 | # verbose opt 185 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' 186 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v 187 | 188 | # normalize http auth 189 | case "${MVNW_PASSWORD:+has-password}" in 190 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; 191 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; 192 | esac 193 | 194 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then 195 | verbose "Found wget ... using wget" 196 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" 197 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then 198 | verbose "Found curl ... using curl" 199 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" 200 | elif set_java_home; then 201 | verbose "Falling back to use Java to download" 202 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" 203 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" 204 | cat >"$javaSource" <<-END 205 | public class Downloader extends java.net.Authenticator 206 | { 207 | protected java.net.PasswordAuthentication getPasswordAuthentication() 208 | { 209 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); 210 | } 211 | public static void main( String[] args ) throws Exception 212 | { 213 | setDefault( new Downloader() ); 214 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); 215 | } 216 | } 217 | END 218 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java 219 | verbose " - Compiling Downloader.java ..." 220 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" 221 | verbose " - Running Downloader.java ..." 222 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" 223 | fi 224 | 225 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 226 | if [ -n "${distributionSha256Sum-}" ]; then 227 | distributionSha256Result=false 228 | if [ "$MVN_CMD" = mvnd.sh ]; then 229 | echo "Checksum validation is not supported for maven-mvnd." >&2 230 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 231 | exit 1 232 | elif command -v sha256sum >/dev/null; then 233 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then 234 | distributionSha256Result=true 235 | fi 236 | elif command -v shasum >/dev/null; then 237 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then 238 | distributionSha256Result=true 239 | fi 240 | else 241 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 242 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 243 | exit 1 244 | fi 245 | if [ $distributionSha256Result = false ]; then 246 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 247 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 248 | exit 1 249 | fi 250 | fi 251 | 252 | # unzip and move 253 | if command -v unzip >/dev/null; then 254 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" 255 | else 256 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" 257 | fi 258 | 259 | # Find the actual extracted directory name (handles snapshots where filename != directory name) 260 | actualDistributionDir="" 261 | 262 | # First try the expected directory name (for regular distributions) 263 | if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then 264 | if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then 265 | actualDistributionDir="$distributionUrlNameMain" 266 | fi 267 | fi 268 | 269 | # If not found, search for any directory with the Maven executable (for snapshots) 270 | if [ -z "$actualDistributionDir" ]; then 271 | # enable globbing to iterate over items 272 | set +f 273 | for dir in "$TMP_DOWNLOAD_DIR"/*; do 274 | if [ -d "$dir" ]; then 275 | if [ -f "$dir/bin/$MVN_CMD" ]; then 276 | actualDistributionDir="$(basename "$dir")" 277 | break 278 | fi 279 | fi 280 | done 281 | set -f 282 | fi 283 | 284 | if [ -z "$actualDistributionDir" ]; then 285 | verbose "Contents of $TMP_DOWNLOAD_DIR:" 286 | verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" 287 | die "Could not find Maven distribution directory in extracted archive" 288 | fi 289 | 290 | verbose "Found extracted Maven distribution directory: $actualDistributionDir" 291 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" 292 | mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" 293 | 294 | clean || : 295 | exec_maven "$@" 296 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.4 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | 82 | $MAVEN_M2_PATH = "$HOME/.m2" 83 | if ($env:MAVEN_USER_HOME) { 84 | $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" 85 | } 86 | 87 | if (-not (Test-Path -Path $MAVEN_M2_PATH)) { 88 | New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null 89 | } 90 | 91 | $MAVEN_WRAPPER_DISTS = $null 92 | if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { 93 | $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" 94 | } else { 95 | $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" 96 | } 97 | 98 | $MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" 99 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 100 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 101 | 102 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 103 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 104 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 105 | exit $? 106 | } 107 | 108 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 109 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 110 | } 111 | 112 | # prepare tmp dir 113 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 114 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 115 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 116 | trap { 117 | if ($TMP_DOWNLOAD_DIR.Exists) { 118 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 119 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 120 | } 121 | } 122 | 123 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 124 | 125 | # Download and Install Apache Maven 126 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 127 | Write-Verbose "Downloading from: $distributionUrl" 128 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 129 | 130 | $webclient = New-Object System.Net.WebClient 131 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 132 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 133 | } 134 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 135 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 136 | 137 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 138 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 139 | if ($distributionSha256Sum) { 140 | if ($USE_MVND) { 141 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 142 | } 143 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 144 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 145 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 146 | } 147 | } 148 | 149 | # unzip and move 150 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 151 | 152 | # Find the actual extracted directory name (handles snapshots where filename != directory name) 153 | $actualDistributionDir = "" 154 | 155 | # First try the expected directory name (for regular distributions) 156 | $expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" 157 | $expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" 158 | if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { 159 | $actualDistributionDir = $distributionUrlNameMain 160 | } 161 | 162 | # If not found, search for any directory with the Maven executable (for snapshots) 163 | if (!$actualDistributionDir) { 164 | Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { 165 | $testPath = Join-Path $_.FullName "bin/$MVN_CMD" 166 | if (Test-Path -Path $testPath -PathType Leaf) { 167 | $actualDistributionDir = $_.Name 168 | } 169 | } 170 | } 171 | 172 | if (!$actualDistributionDir) { 173 | Write-Error "Could not find Maven distribution directory in extracted archive" 174 | } 175 | 176 | Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" 177 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null 178 | try { 179 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 180 | } catch { 181 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 182 | Write-Error "fail to move MAVEN_HOME" 183 | } 184 | } finally { 185 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 186 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 187 | } 188 | 189 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 190 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | thriftr 6 | thriftr 7 | 1.1.8 8 | pom 9 | Apache Thrift Client Server 10 | 11 | Pure R implementation of Apache Thrift. 12 | This library doesn't require any code generation. 13 | To learn more about Thrift go to <https://thrift.apache.org>. 14 | https://github.com/systemincloud/thriftr 15 | 16 | 17 | System in Cloud 18 | http://systemincloud.com/ 19 | 20 | 21 | 22 | 23 | Marek Jagielski 24 | marek.jagielski@gmail.com 25 | 26 | 27 | 28 | 29 | https://github.com/systemincloud/thriftr/issues 30 | 31 | 32 | 33 | UTF-8 34 | yyyy-MM-dd 35 | ${maven.build.timestamp} 36 | 37 | 38 | 39 | 40 | 41 | maven-resources-plugin 42 | 3.0.1 43 | 44 | 45 | copy-resources 46 | validate 47 | 48 | copy-resources 49 | 50 | 51 | ${basedir} 52 | 53 | 54 | resources 55 | true 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | org.codehaus.mojo 64 | exec-maven-plugin 65 | 1.4.0 66 | 67 | R 68 | ${basedir}/.. 69 | 70 | 71 | 72 | build 73 | prepare-package 74 | 75 | exec 76 | 77 | 78 | 79 | CMD 80 | build 81 | 82 | ${project.artifactId} 83 | 84 | 85 | 86 | 87 | check 88 | verify 89 | 90 | exec 91 | 92 | 93 | 94 | CMD 95 | check 96 | 97 | --as-cran 98 | ${project.basedir}/target/${project.artifactId}_${project.version}.tar.gz 99 | --output=${basedir}/target 100 | 101 | 102 | 103 | 104 | install 105 | install 106 | 107 | exec 108 | 109 | 110 | 111 | CMD 112 | INSTALL 113 | 114 | ${project.artifactId} 115 | --with-keep.source 116 | --no-multiarch 117 | 118 | 119 | 120 | 121 | 122 | 123 | maven-antrun-plugin 124 | 1.8 125 | 126 | package 127 | package 128 | 129 | 130 | 131 | 132 | 133 | 134 | run 135 | 136 | 137 | 138 | 139 | 140 | org.codehaus.mojo 141 | build-helper-maven-plugin 142 | 3.0.0 143 | true 144 | 145 | 146 | attach-artifacts 147 | package 148 | attach-artifact 149 | 150 | 151 | 152 | target/${project.artifactId}_${project.version}.tar.gz 153 | tar.gz 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /resources/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: ${project.artifactId} 2 | Type: Package 3 | Title: ${project.name} 4 | Version: ${project.version} 5 | Date: ${date} 6 | Authors@R: c(person("Marek", "Jagielski", 7 | role = c("aut", "cre", "cph"), 8 | email = "marek.jagielski@pm.me"), 9 | person("Lixin", "Yu", 10 | role = c("aut", "cph"))) 11 | Author: Marek Jagielski [aut, cre, cph], Lixin Yu [aut, cph] 12 | Maintainer: Marek Jagielski 13 | Description: ${project.description} 14 | License: MIT + file LICENSE 15 | URL: ${project.url} 16 | BugReports: ${project.issueManagement.url} 17 | Suggests: testthat 18 | Encoding: UTF-8 19 | Imports: 20 | R6, 21 | rly, 22 | stringi 23 | RoxygenNote: 6.0.1 24 | Collate: 25 | 'thrift.R' 26 | 'transport.R' 27 | 'rpc.R' 28 | 'parser.R' 29 | 'protocol_binary.R' 30 | 'server.R' 31 | 'transport_buffered.R' 32 | 'transport_memory.R' 33 | 'transport_socket.R' 34 | 'utils.R' 35 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env Rscript 2 | 3 | library(testthat) 4 | library(thriftr) 5 | 6 | test_check("thriftr") 7 | 8 | -------------------------------------------------------------------------------- /tests/testthat/addressbook.thrift: -------------------------------------------------------------------------------- 1 | include "container.thrift" 2 | 3 | const i16 DEFAULT_LIST_SIZE = 10 4 | 5 | typedef i32 timestamp 6 | 7 | enum PhoneType { 8 | MOBILE = 0, 9 | HOME, 10 | WORK, 11 | } 12 | 13 | struct PhoneNumber { 14 | 1: optional PhoneType type = PhoneType.MOBILE, 15 | 2: optional string number, 16 | 3: optional container.MixItem mix_item, 17 | } 18 | 19 | struct Person { 20 | 1: optional string name, 21 | 2: optional list phones, 22 | 4: optional timestamp created_at, 23 | } 24 | 25 | typedef map PersonMap 26 | 27 | struct AddressBook { 28 | 1: optional PersonMap people, 29 | } 30 | 31 | exception PersonNotExistsError { 32 | 1: optional string message = "Person Not Exists!", 33 | } 34 | 35 | service AddressBookService { 36 | void ping(); 37 | string hello(1: string name); 38 | bool add(1: Person person); 39 | bool remove(1: string name) throws (1: PersonNotExistsError not_exists); 40 | Person get(1: string name) throws (1: PersonNotExistsError not_exists); 41 | AddressBook book(); 42 | list get_phonenumbers(1: string name, 2: i32 count); 43 | map get_phones(1: string name); 44 | bool sleep(1: i32 ms); 45 | } 46 | -------------------------------------------------------------------------------- /tests/testthat/base.thrift: -------------------------------------------------------------------------------- 1 | struct Hello { 2 | 1: optional string name, 3 | 2: optional string greet 4 | } 5 | 6 | enum Code { 7 | OK 8 | WARNING 9 | DANDER 10 | ERROR 11 | UNKNOWN 12 | } 13 | 14 | typedef list codelist 15 | typedef map codemap 16 | typedef set codeset 17 | typedef i64 timestamp 18 | -------------------------------------------------------------------------------- /tests/testthat/const.thrift: -------------------------------------------------------------------------------- 1 | const i16 NEGATIVE_I16 = -10 2 | const double NEGATIVE_DOUBLE = -123.456 3 | 4 | const i16 I16_CONST = 10 5 | const i32 I32_CONST = 100000 6 | const double DOUBLE_CONST = 123.456 7 | 8 | const string DOUBLE_QUOTED_CONST = "hello" 9 | const string SINGLE_QUOTED_CONST = 'hello' 10 | 11 | const string CONST_WITH_SEP1 = "hello", 12 | const string CONST_WITH_SEP2 = "hello"; 13 | 14 | const list I32_LIST_CONST = [1, 2, 3] 15 | const list DOUBLE_LIST_CONST = [1.1, 2.2, 3.3] 16 | const list STRING_LIST_CONST = ["hello", "world"] 17 | const list> I32_LIST_LIST_CONST = [[1, 2, 3], [4, 5, 6]] 18 | const list> DOUBLE_LIST_LIST_CONST = [[1.1, 2.2, 3.3], [4.4, 5.5, 6.6]] 19 | const list> STRING_LIST_LIST_CONST = [["hello", "world"], ["foo", "bar"]] 20 | -------------------------------------------------------------------------------- /tests/testthat/container.thrift: -------------------------------------------------------------------------------- 1 | struct ListStruct { 2 | 1: optional list list_items, 3 | } 4 | 5 | struct ListItem { 6 | 1: optional list list_string, 7 | 2: optional list> list_list_string, 8 | } 9 | 10 | struct MapItem { 11 | 1: optional map map_string, 12 | 2: optional map> map_map_string, 13 | } 14 | 15 | struct MixItem { 16 | 1: optional list> list_map, 17 | 2: optional map> map_list, 18 | } 19 | -------------------------------------------------------------------------------- /tests/testthat/multiplexed.thrift: -------------------------------------------------------------------------------- 1 | service ThingOneService { 2 | bool doThingOne(); 3 | } 4 | 5 | service ThingTwoService { 6 | bool doThingTwo(); 7 | } 8 | -------------------------------------------------------------------------------- /tests/testthat/parent.thrift: -------------------------------------------------------------------------------- 1 | include "base.thrift" 2 | 3 | struct Greet { 4 | 1: optional base.Hello hello, 5 | 2: optional base.timestamp date, 6 | 3: optional base.Code code 7 | 4: optional base.codelist codelist 8 | 5: optional base.codeset codeset 9 | 6: optional base.codemap codemap 10 | } 11 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/comments.thrift: -------------------------------------------------------------------------------- 1 | /* 2 | * C/CPP like comments 3 | */ 4 | 5 | /** 6 | * Silly comments 7 | */ 8 | 9 | # Python like comments 10 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/constants.thrift: -------------------------------------------------------------------------------- 1 | const bool tbool = true 2 | const bool tboolint = 1 3 | const i16 int16 = 3 4 | const i32 int32 = 800 5 | const i64 int64 = 123456789 6 | const string tstr = "hello world" 7 | const double tdouble = 1.3 8 | typedef i32 Integer32 9 | const Integer32 integer32 = 900 10 | const list tlist = [1, 2, 3] 11 | const set tset = [1, 2, 3] 12 | const map tmap1 = {"key": "val"} 13 | const map tmap2 = {"key": 32} 14 | 15 | # https://github.com/eleme/thriftpy/pull/69 16 | enum Country { 17 | US = 1, 18 | UK = 2, 19 | CA = 3, 20 | CN = 4 21 | } 22 | 23 | const Country my_country = Country.CN; 24 | 25 | struct Person { 26 | 1: string name, 27 | 2: Country country = Country.US 28 | } 29 | 30 | const Person tom = {"name": "tom"} 31 | 32 | # https://github.com/eleme/thriftpy/issues/75 33 | const map country_map = { 34 | Country.US: "US", Country.UK: "UK", Country.CA: "CA", Country.CN: "CN"} 35 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/e_dead_include_0.thrift: -------------------------------------------------------------------------------- 1 | include "e_dead_include_1.thrift" 2 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/e_dead_include_1.thrift: -------------------------------------------------------------------------------- 1 | include "e_dead_include_2.thrift" 2 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/e_dead_include_2.thrift: -------------------------------------------------------------------------------- 1 | include "e_dead_include_3.thrift" 2 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/e_dead_include_3.thrift: -------------------------------------------------------------------------------- 1 | include "e_dead_include_1.thrift" 2 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/e_duplicate_field_id.thrift: -------------------------------------------------------------------------------- 1 | struct Foo { 2 | 1: required i32 abc 3 | 1: required i32 efg 4 | } 5 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/e_duplicate_field_name.thrift: -------------------------------------------------------------------------------- 1 | struct Foo { 2 | 1: required i32 abc 3 | 2: required i32 abc 4 | } 5 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/e_grammer_error_at_eof.thrift: -------------------------------------------------------------------------------- 1 | const i32 a = 1 2 | const i32 b = 2 3 | const i32 c = 4 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/e_service_extends_0.thrift: -------------------------------------------------------------------------------- 1 | include "shared.thrift" 2 | 3 | service PingService extends shared.NotExistService { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/e_structs_0.thrift: -------------------------------------------------------------------------------- 1 | struct User { 2 | 1: required string name, 3 | 2: optional string avatar 4 | } 5 | 6 | struct Post { 7 | 1: required string title, 8 | 2: required string content, 9 | 3: required User user, 10 | } 11 | 12 | const Post post = {'title': 'hello world', 'content': 'hello', 13 | 'user': {}} 14 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/e_structs_1.thrift: -------------------------------------------------------------------------------- 1 | struct User { 2 | 1: string name, 3 | 2: i32 age, 4 | 3: string email 5 | } 6 | 7 | const User user = {'avatar': '/avatar.jpg'} 8 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/e_type_error_0.thrift: -------------------------------------------------------------------------------- 1 | const i32 int32 = 1.7; 2 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/e_type_error_1.thrift: -------------------------------------------------------------------------------- 1 | const map> dct = {'key': {'key1': '1'}} 2 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/e_type_error_2.thrift: -------------------------------------------------------------------------------- 1 | struct Person { 2 | 1: string name, 3 | 2: i32 age, 4 | } 5 | 6 | const Person jack = {'name': 'jack', 'age': 1.8} 7 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/e_use_thrift_reserved_keywords.thrift: -------------------------------------------------------------------------------- 1 | const i32 next = 12 2 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/e_value_ref_0.thrift: -------------------------------------------------------------------------------- 1 | const i32 dct = ref; 2 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/e_value_ref_1.thrift: -------------------------------------------------------------------------------- 1 | enum Lang { 2 | CPP = 0, 3 | Python, 4 | Ruby = 2, 5 | } 6 | 7 | const Lang my_lang = 3 8 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/e_value_ref_2.thrift: -------------------------------------------------------------------------------- 1 | struct Cookbook { 2 | 1: string name 3 | } 4 | 5 | const Cookbook cookbook = Cookbook 6 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/enums.thrift: -------------------------------------------------------------------------------- 1 | enum Lang { 2 | C, 3 | Go, 4 | Java, 5 | Javascript, 6 | PHP, 7 | Python, 8 | Ruby, 9 | } 10 | 11 | 12 | enum Country { 13 | US = 1, 14 | UK, 15 | CN, 16 | } 17 | 18 | 19 | enum OS { 20 | OSX, 21 | Win = 3, 22 | Linux 23 | } 24 | 25 | struct Conf { 26 | 1: Lang lang=Lang.PHP 27 | 2: OS os=OS.Linux 28 | } 29 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/include.thrift: -------------------------------------------------------------------------------- 1 | include "included.thrift" 2 | 3 | const included.Timestamp datetime = 1422009523 4 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/included.thrift: -------------------------------------------------------------------------------- 1 | typedef i64 Timestamp 2 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/issue_215.thrift: -------------------------------------------------------------------------------- 1 | struct Test { 2 | 1: required i64 trueNum; 3 | } 4 | 5 | const bool abool = true 6 | const i32 falsevalue = 123 7 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/recursive_union.thrift: -------------------------------------------------------------------------------- 1 | // https://github.com/eleme/thriftpy/issues/157 2 | union Dynamic { 3 | 1: bool boolean; 4 | 2: i64 integer; 5 | 3: double doubl; 6 | 4: string str; 7 | 5: list arr; 8 | 6: map object; 9 | } 10 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/service.thrift: -------------------------------------------------------------------------------- 1 | struct User { 2 | 1: string name, 3 | 2: string address, 4 | } 5 | 6 | struct Email { 7 | 1: string subject, 8 | 2: string content, 9 | } 10 | 11 | exception NetworkError { 12 | 1: string message, 13 | 2: i32 http_code 14 | } 15 | 16 | service EmailService { 17 | void ping () 18 | throws (1: NetworkError network_error) 19 | bool send(1: User recver, 2: User sender, 3: Email email) 20 | throws (1: NetworkError network_error) 21 | } 22 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/service_extends.thrift: -------------------------------------------------------------------------------- 1 | include "shared.thrift" 2 | 3 | 4 | service PingService extends shared.SharedService { 5 | string ping() 6 | } 7 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/shared.thrift: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | /** 21 | * This Thrift file can be included by other Thrift files that want to share 22 | * these definitions. 23 | */ 24 | 25 | namespace cpp shared 26 | namespace d share // "shared" would collide with the eponymous D keyword. 27 | namespace java shared 28 | namespace perl shared 29 | namespace php shared 30 | namespace haxe shared 31 | 32 | struct SharedStruct { 33 | 1: i32 key 34 | 2: string value 35 | } 36 | 37 | service SharedService { 38 | SharedStruct getStruct(1: i32 key) 39 | } 40 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/structs.thrift: -------------------------------------------------------------------------------- 1 | struct Person { 2 | 1: string name, 3 | 2: string address 4 | } 5 | 6 | struct Email { 7 | 1: string subject = 'Subject', 8 | 2: string content, 9 | 3: Person sender, 10 | 4: required Person receiver, 11 | } 12 | 13 | const Email email = {'subject': 'Hello', 'content': 'Long time no see', 14 | 'sender': {'name': 'jack', 'address': 'jack@gmail.com'}, 15 | 'receiver': {'name': 'chao', 'address': 'chao@gmail.com'} 16 | } 17 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/tutorial.thrift: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | # Thrift Tutorial 21 | # Mark Slee (mcslee@facebook.com) 22 | # 23 | # This file aims to teach you how to use Thrift, in a .thrift file. Neato. The 24 | # first thing to notice is that .thrift files support standard shell comments. 25 | # This lets you make your thrift file executable and include your Thrift build 26 | # step on the top line. And you can place comments like this anywhere you like. 27 | # 28 | # Before running this file, you will need to have installed the thrift compiler 29 | # into /usr/local/bin. 30 | 31 | /** 32 | * The first thing to know about are types. The available types in Thrift are: 33 | * 34 | * bool Boolean, one byte 35 | * byte Signed byte 36 | * i16 Signed 16-bit integer 37 | * i32 Signed 32-bit integer 38 | * i64 Signed 64-bit integer 39 | * double 64-bit floating point value 40 | * string String 41 | * binary Blob (byte array) 42 | * map Map from one type to another 43 | * list Ordered list of one type 44 | * set Set of unique elements of one type 45 | * 46 | * Did you also notice that Thrift supports C style comments? 47 | */ 48 | 49 | // Just in case you were wondering... yes. We support simple C comments too. 50 | 51 | /** 52 | * Thrift files can reference other Thrift files to include common struct 53 | * and service definitions. These are found using the current path, or by 54 | * searching relative to any paths specified with the -I compiler flag. 55 | * 56 | * Included objects are accessed using the name of the .thrift file as a 57 | * prefix. i.e. shared.SharedObject 58 | */ 59 | include "shared.thrift" 60 | 61 | /** 62 | * Thrift files can namespace, package, or prefix their output in various 63 | * target languages. 64 | */ 65 | namespace cpp tutorial 66 | namespace d tutorial 67 | namespace java tutorial 68 | namespace php tutorial 69 | namespace perl tutorial 70 | namespace haxe tutorial 71 | 72 | /** 73 | * Thrift lets you do typedefs to get pretty names for your types. Standard 74 | * C style here. 75 | */ 76 | typedef i32 MyInteger 77 | 78 | /** 79 | * Thrift also lets you define constants for use across languages. Complex 80 | * types and structs are specified using JSON notation. 81 | */ 82 | const i32 INT32CONSTANT = 9853 83 | const map MAPCONSTANT = {'hello':'world', 'goodnight':'moon'} 84 | 85 | /** 86 | * You can define enums, which are just 32 bit integers. Values are optional 87 | * and start at 1 if not supplied, C style again. 88 | */ 89 | enum Operation { 90 | ADD = 1, 91 | SUBTRACT = 2, 92 | MULTIPLY = 3, 93 | DIVIDE = 4 94 | } 95 | 96 | /** 97 | * Structs are the basic complex data structures. They are comprised of fields 98 | * which each have an integer identifier, a type, a symbolic name, and an 99 | * optional default value. 100 | * 101 | * Fields can be declared "optional", which ensures they will not be included 102 | * in the serialized output if they aren't set. Note that this requires some 103 | * manual management in some languages. 104 | */ 105 | struct Work { 106 | 1: i32 num1 = 0, 107 | 2: i32 num2, 108 | 3: Operation op, 109 | 4: optional string comment, 110 | } 111 | 112 | /** 113 | * Structs can also be exceptions, if they are nasty. 114 | */ 115 | exception InvalidOperation { 116 | 1: i32 what, 117 | 2: string why 118 | } 119 | 120 | /** 121 | * Ahh, now onto the cool part, defining a service. Services just need a name 122 | * and can optionally inherit from another service using the extends keyword. 123 | */ 124 | service Calculator extends shared.SharedService { 125 | 126 | /** 127 | * A method definition looks like C code. It has a return type, arguments, 128 | * and optionally a list of exceptions that it may throw. Note that argument 129 | * lists and exception lists are specified using the exact same syntax as 130 | * field lists in struct or exception definitions. 131 | */ 132 | 133 | void ping(), 134 | 135 | i32 add(1:i32 num1, 2:i32 num2), 136 | 137 | i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch), 138 | 139 | /** 140 | * This method has a oneway modifier. That means the client only makes 141 | * a request and does not listen for any response at all. Oneway methods 142 | * must be void. 143 | */ 144 | oneway void zip() 145 | 146 | } 147 | 148 | /** 149 | * That just about covers the basics. Take a look in the test/ folder for more 150 | * detailed examples. After you run this file, your generated code shows up 151 | * in folders with names gen-. The generated code isn't too scary 152 | * to look at. It even has pretty indentation. 153 | */ 154 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/type_ref.thrift: -------------------------------------------------------------------------------- 1 | include "type_ref_shared.thrift"; 2 | 3 | const type_ref_shared.Writer jerry = { 4 | 'name': 'jerry', 'age': 26, 5 | 'country': type_ref_shared.Country.US} 6 | const type_ref_shared.Book book = {'writer': jerry, 'name': 'Hello World'} 7 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/type_ref_shared.thrift: -------------------------------------------------------------------------------- 1 | enum Country { 2 | UK = 0, 3 | US = 1, 4 | CN = 2 5 | } 6 | 7 | struct Writer { 8 | 1: string name, 9 | 2: i32 age, 10 | 3: Country country, 11 | } 12 | 13 | struct Book { 14 | 1: string name, 15 | 2: Writer writer, 16 | } 17 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/value_ref.thrift: -------------------------------------------------------------------------------- 1 | include "value_ref_shared.thrift"; 2 | 3 | const i32 abc = 899 4 | 5 | const map> container = value_ref_shared.container; 6 | const list lst = [value_ref_shared.int32, abc, 123]; 7 | -------------------------------------------------------------------------------- /tests/testthat/parser-cases/value_ref_shared.thrift: -------------------------------------------------------------------------------- 1 | const i32 int32 = 39; 2 | const map > container = {'key': [1, 2, 3]}; 3 | -------------------------------------------------------------------------------- /tests/testthat/recursive_definition.thrift: -------------------------------------------------------------------------------- 1 | service PingPong { 2 | Foo echo(1:Foo param) 3 | } 4 | 5 | struct Foo { 6 | 1: optional Bar test, 7 | 2: optional SomeInt some_int, 8 | } 9 | 10 | struct Bar { 11 | 1: optional Foo test, 12 | } 13 | 14 | const SomeInt SOME_INT = [1, 2, 3] 15 | 16 | typedef list SomeInt 17 | -------------------------------------------------------------------------------- /tests/testthat/storm.thrift: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/thrift --gen java:beans,nocamel,hashcode 2 | 3 | /* 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | * 21 | * Contains some contributions under the Thrift Software License. 22 | * Please see doc/old-thrift-license.txt in the Thrift distribution for 23 | * details. 24 | */ 25 | 26 | namespace java backtype.storm.generated 27 | 28 | union JavaObjectArg { 29 | 1: i32 int_arg; 30 | 2: i64 long_arg; 31 | 3: string string_arg; 32 | 4: bool bool_arg; 33 | 5: binary binary_arg; 34 | 6: double double_arg; 35 | } 36 | 37 | struct JavaObject { 38 | 1: required string full_class_name; 39 | 2: required list args_list; 40 | } 41 | 42 | struct NullStruct { 43 | 44 | } 45 | 46 | struct GlobalStreamId { 47 | 1: required string componentId; 48 | 2: required string streamId; 49 | #Going to need to add an enum for the stream type (NORMAL or FAILURE) 50 | } 51 | 52 | union Grouping { 53 | 1: list fields; //empty list means global grouping 54 | 2: NullStruct shuffle; // tuple is sent to random task 55 | 3: NullStruct all; // tuple is sent to every task 56 | 4: NullStruct none; // tuple is sent to a single task (storm's choice) -> allows storm to optimize the topology by bundling tasks into a single process 57 | 5: NullStruct direct; // this bolt expects the source bolt to send tuples directly to it 58 | 6: JavaObject custom_object; 59 | 7: binary custom_serialized; 60 | 8: NullStruct local_or_shuffle; // prefer sending to tasks in the same worker process, otherwise shuffle 61 | } 62 | 63 | struct StreamInfo { 64 | 1: required list output_fields; 65 | 2: required bool direct; 66 | } 67 | 68 | struct ShellComponent { 69 | // should change this to 1: required list execution_command; 70 | 1: string execution_command; 71 | 2: string script; 72 | } 73 | 74 | union ComponentObject { 75 | 1: binary serialized_java; 76 | 2: ShellComponent shell; 77 | 3: JavaObject java_object; 78 | } 79 | 80 | struct ComponentCommon { 81 | 1: required map inputs; 82 | 2: required map streams; //key is stream id 83 | 3: optional i32 parallelism_hint; //how many threads across the cluster should be dedicated to this component 84 | 85 | // component specific configuration respects: 86 | // topology.debug: false 87 | // topology.max.task.parallelism: null // can replace isDistributed with this 88 | // topology.max.spout.pending: null 89 | // topology.kryo.register // this is the only additive one 90 | 91 | // component specific configuration 92 | 4: optional string json_conf; 93 | } 94 | 95 | struct SpoutSpec { 96 | 1: required ComponentObject spout_object; 97 | 2: required ComponentCommon common; 98 | // can force a spout to be non-distributed by overriding the component configuration 99 | // and setting TOPOLOGY_MAX_TASK_PARALLELISM to 1 100 | } 101 | 102 | struct Bolt { 103 | 1: required ComponentObject bolt_object; 104 | 2: required ComponentCommon common; 105 | } 106 | 107 | // not implemented yet 108 | // this will eventually be the basis for subscription implementation in storm 109 | struct StateSpoutSpec { 110 | 1: required ComponentObject state_spout_object; 111 | 2: required ComponentCommon common; 112 | } 113 | 114 | struct StormTopology { 115 | //ids must be unique across maps 116 | // #workers to use is in conf 117 | 1: required map spouts; 118 | 2: required map bolts; 119 | 3: required map state_spouts; 120 | } 121 | 122 | exception AlreadyAliveException { 123 | 1: required string msg; 124 | } 125 | 126 | exception NotAliveException { 127 | 1: required string msg; 128 | } 129 | 130 | exception InvalidTopologyException { 131 | 1: required string msg; 132 | } 133 | 134 | struct TopologySummary { 135 | 1: required string id; 136 | 2: required string name; 137 | 3: required i32 num_tasks; 138 | 4: required i32 num_executors; 139 | 5: required i32 num_workers; 140 | 6: required i32 uptime_secs; 141 | 7: required string status; 142 | } 143 | 144 | struct SupervisorSummary { 145 | 1: required string host; 146 | 2: required i32 uptime_secs; 147 | 3: required i32 num_workers; 148 | 4: required i32 num_used_workers; 149 | 5: required string supervisor_id; 150 | } 151 | 152 | struct ClusterSummary { 153 | 1: required list supervisors; 154 | 2: required i32 nimbus_uptime_secs; 155 | 3: required list topologies; 156 | } 157 | 158 | struct ErrorInfo { 159 | 1: required string error; 160 | 2: required i32 error_time_secs; 161 | } 162 | 163 | struct BoltStats { 164 | 1: required map> acked; 165 | 2: required map> failed; 166 | 3: required map> process_ms_avg; 167 | 4: required map> executed; 168 | 5: required map> execute_ms_avg; 169 | } 170 | 171 | struct SpoutStats { 172 | 1: required map> acked; 173 | 2: required map> failed; 174 | 3: required map> complete_ms_avg; 175 | } 176 | 177 | union ExecutorSpecificStats { 178 | 1: BoltStats bolt; 179 | 2: SpoutStats spout; 180 | } 181 | 182 | // Stats are a map from the time window (all time or a number indicating number of seconds in the window) 183 | // to the stats. Usually stats are a stream id to a count or average. 184 | struct ExecutorStats { 185 | 1: required map> emitted; 186 | 2: required map> transferred; 187 | 3: required ExecutorSpecificStats specific; 188 | } 189 | 190 | struct ExecutorInfo { 191 | 1: required i32 task_start; 192 | 2: required i32 task_end; 193 | } 194 | 195 | struct ExecutorSummary { 196 | 1: required ExecutorInfo executor_info; 197 | 2: required string component_id; 198 | 3: required string host; 199 | 4: required i32 port; 200 | 5: required i32 uptime_secs; 201 | 7: optional ExecutorStats stats; 202 | } 203 | 204 | struct TopologyInfo { 205 | 1: required string id; 206 | 2: required string name; 207 | 3: required i32 uptime_secs; 208 | 4: required list executors; 209 | 5: required string status; 210 | 6: required map> errors; 211 | } 212 | 213 | struct KillOptions { 214 | 1: optional i32 wait_secs; 215 | } 216 | 217 | struct RebalanceOptions { 218 | 1: optional i32 wait_secs; 219 | 2: optional i32 num_workers; 220 | 3: optional map num_executors; 221 | } 222 | 223 | enum TopologyInitialStatus { 224 | ACTIVE = 1, 225 | INACTIVE = 2 226 | } 227 | struct SubmitOptions { 228 | 1: required TopologyInitialStatus initial_status; 229 | } 230 | 231 | service Nimbus { 232 | void submitTopology(1: string name, 2: string uploadedJarLocation, 3: string jsonConf, 4: StormTopology topology) throws (1: AlreadyAliveException e, 2: InvalidTopologyException ite); 233 | void submitTopologyWithOpts(1: string name, 2: string uploadedJarLocation, 3: string jsonConf, 4: StormTopology topology, 5: SubmitOptions options) throws (1: AlreadyAliveException e, 2: InvalidTopologyException ite); 234 | void killTopology(1: string name) throws (1: NotAliveException e); 235 | void killTopologyWithOpts(1: string name, 2: KillOptions options) throws (1: NotAliveException e); 236 | void activate(1: string name) throws (1: NotAliveException e); 237 | void deactivate(1: string name) throws (1: NotAliveException e); 238 | void rebalance(1: string name, 2: RebalanceOptions options) throws (1: NotAliveException e, 2: InvalidTopologyException ite); 239 | 240 | // need to add functions for asking about status of storms, what nodes they're running on, looking at task logs 241 | 242 | string beginFileUpload(); 243 | void uploadChunk(1: string location, 2: binary chunk); 244 | void finishFileUpload(1: string location); 245 | 246 | string beginFileDownload(1: string file); 247 | //can stop downloading chunks when receive 0-length byte array back 248 | binary downloadChunk(1: string id); 249 | 250 | // returns json 251 | string getNimbusConf(); 252 | // stats functions 253 | ClusterSummary getClusterInfo(); 254 | TopologyInfo getTopologyInfo(1: string id) throws (1: NotAliveException e); 255 | //returns json 256 | string getTopologyConf(1: string id) throws (1: NotAliveException e); 257 | StormTopology getTopology(1: string id) throws (1: NotAliveException e); 258 | StormTopology getUserTopology(1: string id) throws (1: NotAliveException e); 259 | } 260 | 261 | struct DRPCRequest { 262 | 1: required string func_args; 263 | 2: required string request_id; 264 | } 265 | 266 | exception DRPCExecutionException { 267 | 1: required string msg; 268 | } 269 | 270 | service DistributedRPC { 271 | string execute(1: string functionName, 2: string funcArgs) throws (1: DRPCExecutionException e); 272 | } 273 | 274 | service DistributedRPCInvocations { 275 | void result(1: string id, 2: string result); 276 | DRPCRequest fetchRequest(1: string functionName); 277 | void failRequest(1: string id); 278 | } 279 | -------------------------------------------------------------------------------- /tests/testthat/test.test_base.R: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env Rscript 2 | 3 | library(testthat) 4 | 5 | context("base") 6 | 7 | ab <- thriftr::t_load("addressbook.thrift") 8 | ab2 <- thriftr::t_load("addressbook.thrift") 9 | 10 | test_that("test_obj_equalcheck", { 11 | expect_equal(ab$Person$new(name="hello"), 12 | ab2$Person$new(name="hello")) 13 | }) 14 | 15 | test_that("test_exc_equalcheck", { 16 | expect_equal(ab$PersonNotExistsError$new("exc"), 17 | ab$PersonNotExistsError$new("exc")) 18 | }) 19 | 20 | test_that("test_cls_equalcheck", { 21 | expect_equal(ab$Person, 22 | ab2$Person) 23 | }) 24 | 25 | test_that("test_isinstancecheck", { 26 | expect_equal(class(ab$Person$new())[[1]], 27 | "Person") 28 | expect_equal(class(ab$Person$new(name = "hello"))[[1]], 29 | "Person") 30 | expect_equal(class(ab$PersonNotExistsError$new())[[1]], 31 | "PersonNotExistsError") 32 | }) 33 | 34 | test_that("test_default_value", { 35 | expect_equal(ab$PhoneNumber$new()$type, 36 | ab$PhoneType$MOBILE) 37 | }) 38 | 39 | test_that("test_parse_spec", { 40 | cases <- list( 41 | list(list(thriftr::TType$I32, NA), "I32"), 42 | list(list(thriftr::TType$STRUCT, ab$PhoneNumber), "PhoneNumber"), 43 | list(list(thriftr::TType$LIST, thriftr::TType$I32), "LIST"), 44 | list(list(thriftr::TType$LIST, list(thriftr::TType$STRUCT, ab$PhoneNumber)), "LIST"), 45 | list(list(thriftr::TType$MAP, list(thriftr::TType$STRING, list( 46 | thriftr::TType$LIST, list(thriftr::TType$MAP, list(thriftr::TType$STRING, thriftr::TType$STRING))))), 47 | "MAP>>") 48 | ) 49 | 50 | for (spec_res in cases) { 51 | expect_equal(thriftr::parse_spec(spec_res[[1]][[1]], spec_res[[1]][[2]]), 52 | spec_res[[2]]) 53 | } 54 | }) 55 | -------------------------------------------------------------------------------- /tests/testthat/test.test_const.R: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env Rscript 2 | 3 | library(testthat) 4 | 5 | context("const") 6 | 7 | thrift <- thriftr::t_load("const.thrift") 8 | 9 | test_that("test_num_const", { 10 | expect_equal(thrift$NEGATIVE_I16, -10) 11 | expect_equal(thrift$NEGATIVE_DOUBLE, -123.456) 12 | 13 | expect_equal(thrift$I16_CONST, 10) 14 | expect_equal(thrift$I32_CONST, 100000) 15 | expect_equal(thrift$DOUBLE_CONST, 123.456) 16 | }) 17 | 18 | test_that("test_string_const", { 19 | expect_equal(thrift$DOUBLE_QUOTED_CONST, "hello") 20 | expect_equal(thrift$SINGLE_QUOTED_CONST, "hello") 21 | }) 22 | 23 | test_that("test_const_with_sep", { 24 | expect_equal(thrift$CONST_WITH_SEP1, "hello") 25 | expect_equal(thrift$CONST_WITH_SEP2, "hello") 26 | }) 27 | 28 | test_that("test_list_const", { 29 | expect_output(str(thrift$I32_LIST_CONST), 30 | 'List of 3 31 | $ : int 1 32 | $ : int 2 33 | $ : int 3', fixed=TRUE) 34 | expect_output(str(thrift$DOUBLE_LIST_CONST), 35 | 'List of 3 36 | $ : num 1.1 37 | $ : num 2.2 38 | $ : num 3.3', fixed=TRUE) 39 | expect_output(str(thrift$STRING_LIST_CONST), 40 | 'List of 2 41 | $ : chr "hello" 42 | $ : chr "world"', fixed=TRUE) 43 | 44 | expect_output(str(thrift$I32_LIST_LIST_CONST), 45 | 'List of 2 46 | $ :List of 3 47 | ..$ : int 1 48 | ..$ : int 2 49 | ..$ : int 3 50 | $ :List of 3 51 | ..$ : int 4 52 | ..$ : int 5 53 | ..$ : int 6', fixed=TRUE) 54 | expect_output(str(thrift$DOUBLE_LIST_LIST_CONST), 55 | 'List of 2 56 | $ :List of 3 57 | ..$ : num 1.1 58 | ..$ : num 2.2 59 | ..$ : num 3.3 60 | $ :List of 3 61 | ..$ : num 4.4 62 | ..$ : num 5.5 63 | ..$ : num 6.6', fixed=TRUE) 64 | expect_output(str(thrift$STRING_LIST_LIST_CONST), 65 | 'List of 2 66 | $ :List of 2 67 | ..$ : chr "hello" 68 | ..$ : chr "world" 69 | $ :List of 2 70 | ..$ : chr "foo" 71 | ..$ : chr "bar"', fixed=TRUE) 72 | }) 73 | -------------------------------------------------------------------------------- /tests/testthat/test.test_parser.R: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env Rscript 2 | 3 | library(testthat) 4 | 5 | context("parser") 6 | 7 | test_that("test_comments", { 8 | thriftr::t_load("parser-cases/comments.thrift") 9 | }) 10 | 11 | test_that("test_constants", { 12 | thrift <- thriftr::t_load("parser-cases/constants.thrift") 13 | expect_that(thrift$tbool, equals(TRUE)) 14 | expect_that(thrift$tboolint, equals(TRUE)) 15 | expect_that(thrift$int16, equals(3)) 16 | expect_that(thrift$int32, equals(800)) 17 | expect_that(thrift$int64, equals(123456789)) 18 | expect_that(thrift$tstr, equals("hello world")) 19 | expect_that(thrift$integer32, equals(900)) 20 | expect_that(thrift$tdouble, equals(1.3)) 21 | expect_that(length(setdiff(thrift$tlist, list(1, 2, 3))), equals(0)) 22 | expect_that(length(setdiff(thrift$tset, list(1, 2, 3))), equals(0)) 23 | expect_that(thrift$tmap1[["key"]], equals("val")) 24 | expect_that(thrift$tmap2[["key"]], equals(32)) 25 | expect_that(thrift$my_country, equals(4)) 26 | expect_that(thrift$Person$new(name = "tom")$country, equals(1)) 27 | expect_that(thrift$Person$new(name = "tom")$name, equals("tom")) 28 | expect_that(thrift$country_map[[toString(thrift$Country$US)]], equals("US")) 29 | expect_that(thrift$country_map[[toString(thrift$Country$UK)]], equals("UK")) 30 | expect_that(thrift$country_map[[toString(thrift$Country$CA)]], equals("CA")) 31 | expect_that(thrift$country_map[[toString(thrift$Country$CN)]], equals("CN")) 32 | }) 33 | 34 | test_that("test_include", { 35 | thrift <- thriftr::t_load("parser-cases/include.thrift") 36 | expect_that(thrift$datetime, equals(1422009523)) 37 | }) 38 | 39 | test_that("test_tutorial", { 40 | thrift <- thriftr::t_load("parser-cases/tutorial.thrift", 41 | include_dirs = list("./parser-cases")) 42 | expect_equal(thrift$INT32CONSTANT, 9853) 43 | expect_equal(thrift$MAPCONSTANT[["hello"]], "world") 44 | expect_equal(thrift$MAPCONSTANT[["goodnight"]], "moon") 45 | expect_equal(thrift$Operation$ADD, 1) 46 | expect_equal(thrift$Operation$SUBTRACT, 2) 47 | expect_equal(thrift$Operation$MULTIPLY, 3) 48 | expect_equal(thrift$Operation$DIVIDE, 4) 49 | work <- thrift$Work$new() 50 | expect_equal(work$num1, 0) 51 | expect_equal(work$num2, NA) 52 | expect_equal(work$op, NA) 53 | expect_equal(work$comment, NA) 54 | expect_equal(thrift$Calculator$thrift_services[[1]], "ping") 55 | expect_equal(thrift$Calculator$thrift_services[[2]], "add") 56 | expect_equal(thrift$Calculator$thrift_services[[3]], "calculate") 57 | expect_equal(thrift$Calculator$thrift_services[[4]], "zip") 58 | expect_equal(thrift$Calculator$thrift_services[[5]], "getStruct") 59 | }) 60 | 61 | test_that("test_e_type_error", { 62 | expect_error(thriftr::t_load("parser-cases/e_type_error_0.thrift"), 63 | "\\[ThriftParserError\\]Type error for constant int32 at line 1") 64 | expect_error(thriftr::t_load("parser-cases/e_type_error_1.thrift"), 65 | "\\[ThriftParserError\\]Type error for constant dct at line 1") 66 | expect_error(thriftr::t_load("parser-cases/e_type_error_2.thrift"), 67 | "\\[ThriftParserError\\]Type error for constant jack at line 6") 68 | }) 69 | 70 | test_that("test_value_ref", { 71 | thrift <- thriftr::t_load("parser-cases/value_ref.thrift") 72 | expect_equal(thrift$container$key[[1]], 1) 73 | expect_equal(thrift$container$key[[2]], 2) 74 | expect_equal(thrift$container$key[[3]], 3) 75 | expect_equal(thrift$lst[[1]], 39) 76 | expect_equal(thrift$lst[[2]], 899) 77 | expect_equal(thrift$lst[[3]], 123) 78 | }) 79 | 80 | test_that("test_type_ref", { 81 | thrift <- thriftr::t_load("parser-cases/type_ref.thrift") 82 | expect_equal(thrift$jerry$name, "jerry") 83 | expect_equal(thrift$jerry$age, 26) 84 | expect_equal(thrift$jerry$country, thrift$type_ref_shared$Country$US) 85 | expect_equal(thrift$book$name, "Hello World") 86 | expect_equal(thrift$book$writer$name, "jerry") 87 | expect_equal(thrift$book$writer$age, 26) 88 | expect_equal(thrift$book$writer$country, thrift$type_ref_shared$Country$US) 89 | }) 90 | 91 | test_that("test_e_value_ref", { 92 | expect_error(thriftr::t_load("parser-cases/e_value_ref_0.thrift"), 93 | "\\[ThriftParserError\\]Can't find name ref at line 1") 94 | expect_error(thriftr::t_load("parser-cases/e_value_ref_1.thrift"), 95 | "\\[ThriftParserError\\]Couldn't find a named value in enum Lang for value 3") 96 | expect_error(thriftr::t_load("parser-cases/e_value_ref_2.thrift"), 97 | "\\[ThriftParserError\\]No enum value or constant found named Cookbook") 98 | }) 99 | 100 | test_that("test_enums", { 101 | thrift <- thriftr::t_load("parser-cases/enums.thrift") 102 | expect_equal(thrift$Lang$C, 0) 103 | expect_equal(thrift$Lang$Go, 1) 104 | expect_equal(thrift$Lang$Java, 2) 105 | expect_equal(thrift$Lang$Javascript, 3) 106 | expect_equal(thrift$Lang$PHP, 4) 107 | expect_equal(thrift$Lang$Python, 5) 108 | expect_equal(thrift$Lang$Ruby, 6) 109 | expect_equal(thrift$Country$US, 1) 110 | expect_equal(thrift$Country$UK, 2) 111 | expect_equal(thrift$Country$CN, 3) 112 | expect_equal(thrift$OS$OSX, 0) 113 | expect_equal(thrift$OS$Win, 3) 114 | expect_equal(thrift$OS$Linux, 4) 115 | }) 116 | 117 | test_that("test_structs", { 118 | thrift <- thriftr::t_load("parser-cases/structs.thrift") 119 | expect_equal(thrift$Person$thrift_spec[["1"]][[1]], thriftr::TType$STRING) 120 | expect_equal(thrift$Person$thrift_spec[["1"]][[2]], "name") 121 | expect_equal(thrift$Person$thrift_spec[["1"]][[3]], FALSE) 122 | expect_equal(thrift$Person$thrift_spec[["2"]][[1]], thriftr::TType$STRING) 123 | expect_equal(thrift$Person$thrift_spec[["2"]][[2]], "address") 124 | expect_equal(thrift$Person$thrift_spec[["2"]][[3]], FALSE) 125 | expect_equal(thrift$Person$default_spec[[1]][[1]], "name") 126 | expect_equal(thrift$Person$default_spec[[1]][[2]], NA) 127 | expect_equal(thrift$Person$default_spec[[2]][[1]], "address") 128 | expect_equal(thrift$Person$default_spec[[2]][[2]], NA) 129 | expect_equal(thrift$Email$thrift_spec[["1"]][[1]], thriftr::TType$STRING) 130 | expect_equal(thrift$Email$thrift_spec[["1"]][[2]], "subject") 131 | expect_equal(thrift$Email$thrift_spec[["1"]][[3]], FALSE) 132 | expect_equal(thrift$Email$thrift_spec[["2"]][[1]], thriftr::TType$STRING) 133 | expect_equal(thrift$Email$thrift_spec[["2"]][[2]], "content") 134 | expect_equal(thrift$Email$thrift_spec[["2"]][[3]], FALSE) 135 | expect_equal(thrift$Email$thrift_spec[["3"]][[1]], thriftr::TType$STRUCT) 136 | expect_equal(thrift$Email$thrift_spec[["3"]][[2]], "sender") 137 | expect_equal(thrift$Email$thrift_spec[["3"]][[3]], thrift$Person) 138 | expect_equal(thrift$Email$thrift_spec[["3"]][[4]], FALSE) 139 | expect_equal(thrift$Email$thrift_spec[["4"]][[1]], thriftr::TType$STRUCT) 140 | expect_equal(thrift$Email$thrift_spec[["4"]][[2]], "receiver") 141 | expect_equal(thrift$Email$thrift_spec[["4"]][[3]], thrift$Person) 142 | expect_equal(thrift$Email$thrift_spec[["4"]][[4]], TRUE) 143 | expect_equal(thrift$Email$default_spec[[1]][[1]], "subject") 144 | expect_equal(thrift$Email$default_spec[[1]][[2]], "Subject") 145 | expect_equal(thrift$Email$default_spec[[2]][[1]], "content") 146 | expect_equal(thrift$Email$default_spec[[2]][[2]], NA) 147 | expect_equal(thrift$Email$default_spec[[3]][[1]], "sender") 148 | expect_equal(thrift$Email$default_spec[[3]][[2]], NA) 149 | expect_equal(thrift$Email$default_spec[[4]][[1]], "receiver") 150 | expect_equal(thrift$Email$default_spec[[4]][[2]], NA) 151 | expect_equal(thrift$email$subject, "Hello") 152 | expect_equal(thrift$email$content, "Long time no see") 153 | expect_equal(thrift$email$sender$name, "jack") 154 | expect_equal(thrift$email$sender$address, "jack@gmail.com") 155 | expect_equal(thrift$email$receiver$name, "chao") 156 | expect_equal(thrift$email$receiver$address, "chao@gmail.com") 157 | }) 158 | 159 | test_that("test_e_structs", { 160 | expect_error(thriftr::t_load("parser-cases/e_structs_0.thrift"), 161 | "\\[ThriftParserError\\]Field name was required to create constant for type User") 162 | expect_error(thriftr::t_load("parser-cases/e_structs_1.thrift"), 163 | "\\[ThriftParserError\\]No field named avatar was found in struct of type User") 164 | }) 165 | 166 | test_that("test_service", { 167 | thrift <- thriftr::t_load("parser-cases/service.thrift") 168 | expect_equal(thrift$EmailService$thrift_services[[1]], "ping") 169 | expect_equal(thrift$EmailService$thrift_services[[2]], "send") 170 | expect_equal(length(names(thrift$EmailService$ping_args$thrift_spec)), 0) 171 | expect_equal(length(thrift$EmailService$ping_args$default_spec), 0) 172 | expect_equal(thrift$EmailService$ping_result$thrift_spec[["1"]][[1]], thriftr::TType$STRUCT) 173 | expect_equal(thrift$EmailService$ping_result$thrift_spec[["1"]][[2]], "network_error") 174 | expect_equal(thrift$EmailService$ping_result$thrift_spec[["1"]][[3]], thrift$NetworkError) 175 | expect_equal(thrift$EmailService$ping_result$thrift_spec[["1"]][[4]], FALSE) 176 | expect_equal(thrift$EmailService$ping_result$default_spec[[1]][[1]], "network_error") 177 | expect_equal(thrift$EmailService$ping_result$default_spec[[1]][[2]], NA) 178 | expect_equal(thrift$EmailService$send_args$thrift_spec[["1"]][[1]], thriftr::TType$STRUCT) 179 | expect_equal(thrift$EmailService$send_args$thrift_spec[["1"]][[2]], "recver") 180 | expect_equal(thrift$EmailService$send_args$thrift_spec[["1"]][[3]], thrift$User) 181 | expect_equal(thrift$EmailService$send_args$thrift_spec[["1"]][[4]], FALSE) 182 | expect_equal(thrift$EmailService$send_args$thrift_spec[["2"]][[1]], thriftr::TType$STRUCT) 183 | expect_equal(thrift$EmailService$send_args$thrift_spec[["2"]][[2]], "sender") 184 | expect_equal(thrift$EmailService$send_args$thrift_spec[["2"]][[3]], thrift$User) 185 | expect_equal(thrift$EmailService$send_args$thrift_spec[["2"]][[4]], FALSE) 186 | expect_equal(thrift$EmailService$send_args$thrift_spec[["3"]][[1]], thriftr::TType$STRUCT) 187 | expect_equal(thrift$EmailService$send_args$thrift_spec[["3"]][[2]], "email") 188 | expect_equal(thrift$EmailService$send_args$thrift_spec[["3"]][[3]], thrift$Email) 189 | expect_equal(thrift$EmailService$send_args$thrift_spec[["3"]][[4]], FALSE) 190 | expect_equal(thrift$EmailService$send_args$default_spec[[1]][[1]], "recver") 191 | expect_equal(thrift$EmailService$send_args$default_spec[[1]][[2]], NA) 192 | expect_equal(thrift$EmailService$send_args$default_spec[[2]][[1]], "sender") 193 | expect_equal(thrift$EmailService$send_args$default_spec[[2]][[2]], NA) 194 | expect_equal(thrift$EmailService$send_args$default_spec[[3]][[1]], "email") 195 | expect_equal(thrift$EmailService$send_args$default_spec[[3]][[2]], NA) 196 | expect_equal(thrift$EmailService$send_result$thrift_spec[["0"]][[1]], thriftr::TType$BOOL) 197 | expect_equal(thrift$EmailService$send_result$thrift_spec[["0"]][[2]], "success") 198 | expect_equal(thrift$EmailService$send_result$thrift_spec[["0"]][[3]], FALSE) 199 | expect_equal(thrift$EmailService$send_result$thrift_spec[["1"]][[1]], thriftr::TType$STRUCT) 200 | expect_equal(thrift$EmailService$send_result$thrift_spec[["1"]][[2]], "network_error") 201 | expect_equal(thrift$EmailService$send_result$thrift_spec[["1"]][[3]], thrift$NetworkError) 202 | expect_equal(thrift$EmailService$send_result$thrift_spec[["1"]][[4]], FALSE) 203 | expect_equal(thrift$EmailService$send_result$default_spec[[1]][[1]], "success") 204 | expect_equal(thrift$EmailService$send_result$default_spec[[1]][[2]], NA) 205 | expect_equal(thrift$EmailService$send_result$default_spec[[2]][[1]], "network_error") 206 | expect_equal(thrift$EmailService$send_result$default_spec[[2]][[2]], NA) 207 | }) 208 | 209 | test_that("test_service_extends", { 210 | thrift <- thriftr::t_load("parser-cases/service_extends.thrift") 211 | expect_equal(thrift$PingService$thrift_services[[1]], "ping") 212 | expect_equal(thrift$PingService$thrift_services[[2]], "getStruct") 213 | }) 214 | 215 | test_that("test_e_service_extends", { 216 | expect_error(thrift <- thriftr::t_load("parser-cases/e_service_extends_0.thrift"), 217 | "\\[ThriftParserError\\]Can't find service shared.NotExistService for service PingService to extend") 218 | }) 219 | 220 | test_that("test_e_dead_include", { 221 | expect_error(thriftr::t_load("parser-cases/e_dead_include_0.thrift"), 222 | "\\[ThriftParserError\\]Dead including") 223 | }) 224 | 225 | test_that("test_e_grammer_error_at_eof", { 226 | expect_error(thriftr::t_load("parser-cases/e_grammer_error_at_eof.thrift"), 227 | "\\[ThriftGrammerError\\]Grammar error at EOF") 228 | }) 229 | 230 | test_that("test_e_use_thrift_reserved_keywords", { 231 | expect_error(thriftr::t_load("parser-cases/e_use_thrift_reserved_keywords.thrift"), 232 | "\\[ThriftLexerError\\]Cannot use reserved language keyword: next at line 1") 233 | }) 234 | 235 | test_that("test_e_duplicate_field_id_or_name", { 236 | expect_error(thriftr::t_load("parser-cases/e_duplicate_field_id.thrift"), 237 | "\\[ThriftGrammerError\\]'1:efg' field identifier/name has already been used") 238 | expect_error(thriftr::t_load("parser-cases/e_duplicate_field_name.thrift"), 239 | "\\[ThriftGrammerError\\]\\'2:abc\\' field identifier/name has already been used") 240 | }) 241 | 242 | test_that("test_thrift_meta", { 243 | thrift <- thriftr::t_load("parser-cases/tutorial.thrift") 244 | meta <- thrift$thrift_meta 245 | expect_equal(meta$consts[[1]], thrift$INT32CONSTANT) 246 | expect_equal(meta$consts[[2]], thrift$MAPCONSTANT) 247 | expect_equal(meta$enums[[1]], thrift$Operation) 248 | expect_equal(meta$structs[[1]], thrift$Work) 249 | expect_equal(meta$exceptions[[1]], thrift$InvalidOperation) 250 | expect_equal(meta$services[[1]], thrift$Calculator) 251 | expect_equal(meta$includes[[1]], thrift$shared) 252 | }) 253 | 254 | test_that("test_recursive_union", { 255 | thrift <- thriftr::t_load("parser-cases/recursive_union.thrift") 256 | expect_equal(thrift$Dynamic$thrift_spec[["1"]][[1]], thriftr::TType$BOOL) 257 | expect_equal(thrift$Dynamic$thrift_spec[["1"]][[2]], "boolean") 258 | expect_equal(thrift$Dynamic$thrift_spec[["1"]][[3]], FALSE) 259 | expect_equal(thrift$Dynamic$thrift_spec[["2"]][[1]], thriftr::TType$I64) 260 | expect_equal(thrift$Dynamic$thrift_spec[["2"]][[2]], "integer") 261 | expect_equal(thrift$Dynamic$thrift_spec[["2"]][[3]], FALSE) 262 | expect_equal(thrift$Dynamic$thrift_spec[["3"]][[1]], thriftr::TType$DOUBLE) 263 | expect_equal(thrift$Dynamic$thrift_spec[["3"]][[2]], "doubl") 264 | expect_equal(thrift$Dynamic$thrift_spec[["3"]][[3]], FALSE) 265 | expect_equal(thrift$Dynamic$thrift_spec[["4"]][[1]], thriftr::TType$STRING) 266 | expect_equal(thrift$Dynamic$thrift_spec[["4"]][[2]], "str") 267 | expect_equal(thrift$Dynamic$thrift_spec[["4"]][[3]], FALSE) 268 | expect_equal(thrift$Dynamic$thrift_spec[["5"]][[1]], thriftr::TType$LIST) 269 | expect_equal(thrift$Dynamic$thrift_spec[["5"]][[2]], "arr") 270 | expect_equal(thrift$Dynamic$thrift_spec[["5"]][[3]][[1]], thriftr::TType$STRUCT) 271 | expect_equal(thrift$Dynamic$thrift_spec[["5"]][[3]][[2]], thrift$Dynamic) 272 | expect_equal(thrift$Dynamic$thrift_spec[["5"]][[4]], FALSE) 273 | expect_equal(thrift$Dynamic$thrift_spec[["6"]][[1]], thriftr::TType$MAP) 274 | expect_equal(thrift$Dynamic$thrift_spec[["6"]][[2]], "object") 275 | expect_equal(thrift$Dynamic$thrift_spec[["6"]][[3]][[1]], thriftr::TType$STRING) 276 | expect_equal(thrift$Dynamic$thrift_spec[["6"]][[3]][[2]][[1]], thriftr::TType$STRUCT) 277 | expect_equal(thrift$Dynamic$thrift_spec[["6"]][[3]][[2]][[2]], thrift$Dynamic) 278 | expect_equal(thrift$Dynamic$thrift_spec[["6"]][[4]], FALSE) 279 | }) 280 | 281 | test_that("test_issue_215", { 282 | thrift <- thriftr::t_load("parser-cases/issue_215.thrift") 283 | expect_equal(thrift$abool, TRUE) 284 | expect_equal(thrift$falsevalue, 123) 285 | }) 286 | -------------------------------------------------------------------------------- /tests/testthat/test.test_protocol_binary.R: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env Rscript 2 | 3 | library(testthat) 4 | 5 | context("type_protocol_binary") 6 | 7 | TItem <- thriftr::to_proper_struct( 8 | list( 9 | list("1", list(thriftr::TType$I32, 'id', FALSE)), 10 | list("2", list(thriftr::TType$LIST, 'phones', list(thriftr::TType$STRING), FALSE)) 11 | ), 12 | list( 13 | list('id', NA), 14 | list('phones', NA) 15 | ) 16 | ) 17 | 18 | test_that("test_pack_i8", { 19 | b <- thriftr::TMemoryBuffer$new() 20 | thriftr::binary_write_val(b, thriftr::TType$I08, 123) 21 | expect_equal(thriftr::hexlify(b$getvalue()), "7b") 22 | }) 23 | 24 | test_that("test_unpack_i8", { 25 | b <- thriftr::TMemoryBuffer$new(charToRaw("{")) 26 | expect_equal(thriftr::binary_read_val(b, thriftr::TType$I08), 123) 27 | }) 28 | 29 | test_that("test_pack_i16", { 30 | b <- thriftr::TMemoryBuffer$new() 31 | thriftr::binary_write_val(b, thriftr::TType$I16, 12345) 32 | expect_equal(thriftr::hexlify(b$getvalue()), "30 39") 33 | }) 34 | 35 | test_that("test_unpack_i16", { 36 | b <- thriftr::TMemoryBuffer$new(charToRaw("09")) 37 | expect_equal(thriftr::binary_read_val(b, thriftr::TType$I16), 12345) 38 | }) 39 | 40 | test_that("test_pack_i32", { 41 | b <- thriftr::TMemoryBuffer$new() 42 | thriftr::binary_write_val(b, thriftr::TType$I32, 1234567890) 43 | expect_equal(thriftr::hexlify(b$getvalue()), "49 96 02 d2") 44 | }) 45 | 46 | test_that("test_unpack_i32", { 47 | b <- thriftr::TMemoryBuffer$new(as.raw(strtoi(c("49", "96", "02", "d2"), 16L))) 48 | expect_equal(thriftr::binary_read_val(b, thriftr::TType$I32)[[1]], 1234567890) 49 | }) 50 | 51 | test_that("test_pack_i64", { 52 | b <- thriftr::TMemoryBuffer$new() 53 | thriftr::binary_write_val(b, thriftr::TType$I64, c(287445236, 2112454933)) 54 | expect_equal(thriftr::hexlify(b$getvalue()), "11 22 10 f4 7d e9 81 15") 55 | }) 56 | 57 | test_that("test_unpack_i64", { 58 | b <- thriftr::TMemoryBuffer$new(as.raw(strtoi(c("11", "22", "10", "f4", "7d", "e9", "81", "15"), 16L))) 59 | l <- thriftr::binary_read_val(b, thriftr::TType$I64) 60 | expect_equal(l[1], 287445236) 61 | expect_equal(l[2], 2112454933) 62 | }) 63 | 64 | test_that("test_pack_double", { 65 | b <- thriftr::TMemoryBuffer$new() 66 | thriftr::binary_write_val(b, thriftr::TType$DOUBLE, 1234567890.1234567) 67 | expect_equal(thriftr::hexlify(b$getvalue()), "41 d2 65 80 b4 87 e6 b7") 68 | }) 69 | 70 | test_that("test_unpack_double", { 71 | b <- thriftr::TMemoryBuffer$new(as.raw(strtoi(c("41", "d2", "65", "80", "b4", "87", "e6", "b7"), 16L))) 72 | expect_equal(thriftr::binary_read_val(b, thriftr::TType$DOUBLE)[[1]], 1234567890.1234567, tolerance = 1e-17) 73 | }) 74 | 75 | test_that("test_pack_string", { 76 | b <- thriftr::TMemoryBuffer$new() 77 | thriftr::binary_write_val(b, thriftr::TType$STRING, "hello world!") 78 | expect_equal(thriftr::hexlify(b$getvalue()), "00 00 00 0c 68 65 6c 6c 6f 20 77 6f 72 6c 64 21") 79 | 80 | b <- thriftr::TMemoryBuffer$new() 81 | thriftr::binary_write_val(b, thriftr::TType$STRING, "你好世界") 82 | expect_equal(thriftr::hexlify(b$getvalue()), "00 00 00 0c e4 bd a0 e5 a5 bd e4 b8 96 e7 95 8c") 83 | }) 84 | 85 | test_that("test_unpack_string", { 86 | b <- thriftr::TMemoryBuffer$new(as.raw(strtoi(c("00", "00", "00", "0c", "e4", "bd", "a0", 87 | "e5", "a5", "bd", "e4", "b8", "96", "e7", "95", "8c"), 16L))) 88 | expect_equal(thriftr::binary_read_val(b, thriftr::TType$STRING), "你好世界") 89 | }) 90 | 91 | test_that("test_unpack_binary", { 92 | b <- thriftr::TMemoryBuffer$new(as.raw(strtoi(c("00", "00", "00", "0c", "e4", "bd", "a0", 93 | "e5", "a5", "bd", "e4", "b8", "96", "e7", "95", "8c"), 16L))) 94 | expect_equal(thriftr::binary_read_val(b, thriftr::TType$STRING, decode_response = FALSE), charToRaw("你好世界")) 95 | }) 96 | 97 | test_that("test_write_message_begin", { 98 | b <- thriftr::TMemoryBuffer$new() 99 | thriftr::TBinaryProtocol$new(b)$write_message_begin("test", thriftr::TType$STRING, 1) 100 | expect_equal(thriftr::hexlify(b$getvalue()), "80 01 00 0b 00 00 00 04 74 65 73 74 00 00 00 01") 101 | }) 102 | 103 | test_that("test_write_message_begin_not_strict", { 104 | b <- thriftr::TMemoryBuffer$new() 105 | thriftr::TBinaryProtocol$new(b, strict_write = FALSE)$write_message_begin("test", thriftr::TType$STRING, 1) 106 | expect_equal(thriftr::hexlify(b$getvalue()), "00 00 00 04 74 65 73 74 0b 00 00 00 01") 107 | }) 108 | 109 | test_that("test_read_message_begin", { 110 | b <- thriftr::TMemoryBuffer$new(as.raw(strtoi(c("80", "01", "00", "0b", "00", "00", "00", "04", "74", "65", "73", "74", "00", "00", "00", "01"), 16L))) 111 | res <- thriftr::TBinaryProtocol$new(b)$read_message_begin() 112 | expect_equal(res[[1]], "test") 113 | expect_equal(res[[2]], thriftr::TType$STRING) 114 | expect_equal(res[[3]], 1) 115 | }) 116 | 117 | test_that("test_read_message_begin_not_strict", { 118 | b <- thriftr::TMemoryBuffer$new(as.raw(strtoi(c("00", "00", "00", "04", "74", "65", "73", "74", "0b", "00", "00", "00", "01"), 16L))) 119 | res <- thriftr::TBinaryProtocol$new(b, strict_read = FALSE)$read_message_begin() 120 | expect_equal(res[[1]], "test") 121 | expect_equal(res[[2]], thriftr::TType$STRING) 122 | expect_equal(res[[3]], 1) 123 | }) 124 | 125 | test_that("test_write_struct", { 126 | item <- TItem$new(id=123, phones=list("123456", "abcdef")) 127 | b <- thriftr::TMemoryBuffer$new() 128 | thriftr::TBinaryProtocol$new(b)$write_struct(item) 129 | expect_equal(thriftr::hexlify(b$getvalue()), "08 00 01 00 00 00 7b 0f 00 02 0b 00 00 00 02 00 00 00 06 31 32 33 34 35 36 00 00 00 06 61 62 63 64 65 66 00") 130 | }) 131 | 132 | test_that("test_read_struct", { 133 | b <- thriftr::TMemoryBuffer$new(as.raw(strtoi(c("08", "00", "01", "00", "00", "00", "7b", "0f", "00","02", "0b", "00", "00", "00", 134 | "02", "00", "00", "00", "06", "31", "32", "33", "34", "35", "36", "00", "00", "00", "06", "61", "62", "63", "64", "65", "66", "00"), 16L))) 135 | item <- TItem$new(id=123, phones=list("123456", "abcdef")) 136 | item2 <- TItem$new() 137 | thriftr::TBinaryProtocol$new(b)$read_struct(item2) 138 | expect_equal(item$id, item2$id) 139 | expect_equal(item$phones, item2$phones) 140 | }) 141 | 142 | test_that("test_write_empty_struct", { 143 | item <- TItem$new() 144 | b <- thriftr::TMemoryBuffer$new() 145 | thriftr::TBinaryProtocol$new(b)$write_struct(item) 146 | expect_equal(thriftr::hexlify(b$getvalue()), "00") 147 | }) 148 | 149 | test_that("test_read_empty_struct", { 150 | b <- thriftr::TMemoryBuffer$new(as.raw(strtoi(c("00"), 16L))) 151 | item <- TItem$new() 152 | item2 <- TItem$new() 153 | thriftr::TBinaryProtocol$new(b)$read_struct(item2) 154 | expect_equal(item$id, item2$id) 155 | expect_equal(item$phones, item2$phones) 156 | }) 157 | 158 | test_that("test_write_huge_struct", { 159 | item <- TItem$new(id=12345, phones=rep("1234567890", 10000)) 160 | b <- thriftr::TMemoryBuffer$new() 161 | thriftr::TBinaryProtocol$new(b)$write_struct(item) 162 | }) 163 | -------------------------------------------------------------------------------- /tests/testthat/test.test_recursive_definition.R: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env Rscript 2 | 3 | library(testthat) 4 | 5 | context("recursive_definition") 6 | 7 | test_that("test_recursive_definition", { 8 | thrift <- thriftr::t_load("recursive_definition.thrift") 9 | expect_equal(thrift$Bar$thrift_spec[["1"]][[1]], 12) 10 | expect_equal(thrift$Bar$thrift_spec[["1"]][[2]], "test") 11 | expect_equal(thrift$Bar$thrift_spec[["1"]][[3]], thrift$Foo) 12 | expect_equal(thrift$Bar$thrift_spec[["1"]][[4]], FALSE) 13 | expect_equal(thrift$Foo$thrift_spec[["1"]][[1]], 12) 14 | expect_equal(thrift$Foo$thrift_spec[["1"]][[2]], 'test') 15 | expect_equal(thrift$Foo$thrift_spec[["1"]][[3]], thrift$Bar) 16 | expect_equal(thrift$Foo$thrift_spec[["1"]][[4]], FALSE) 17 | expect_equal(thrift$Foo$thrift_spec[["2"]][[1]], 15) 18 | expect_equal(thrift$Foo$thrift_spec[["2"]][[2]], 'some_int') 19 | expect_equal(thrift$Foo$thrift_spec[["2"]][[3]], 8) 20 | expect_equal(thrift$Foo$thrift_spec[["2"]][[4]], FALSE) 21 | }) 22 | 23 | test_that("test_const", { 24 | thrift <- thriftr::t_load("recursive_definition.thrift") 25 | expect_equal(thrift$SOME_INT[[1]], 1) 26 | expect_equal(thrift$SOME_INT[[2]], 2) 27 | expect_equal(thrift$SOME_INT[[3]], 3) 28 | }) 29 | -------------------------------------------------------------------------------- /tests/testthat/test.test_socket.R: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env Rscript 2 | 3 | library(testthat) 4 | library(parallel) 5 | 6 | context("socket") 7 | 8 | test_socket <- function(server_socket, client_socket) { 9 | 10 | server <- function() { 11 | server_socket$listen() 12 | conn <- server_socket$accept() 13 | buff3 <- conn$read(12) 14 | conn$write(buff3) 15 | conn$close() 16 | } 17 | 18 | client <- function () { 19 | Sys.sleep(2) 20 | client_socket$open() 21 | buff <- charToRaw("Hello World!") 22 | client_socket$write(buff) 23 | buff2 <- client_socket$read(12) 24 | client_socket$close() 25 | all(buff == buff2) 26 | } 27 | expect_true(mclapply(list(server, client), function(x) { 28 | x() 29 | }, mc.cores=2)[[2]]) 30 | } 31 | 32 | test_that("test_inet_socket", { 33 | if(.Platform$OS.type == "unix") { 34 | server_socket <- thriftr::TServerSocket$new(host="localhost", port=6011) 35 | client_socket <- thriftr::TSocket$new(host="localhost", port=6011) 36 | 37 | test_socket(server_socket, client_socket) 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /tests/testthat/test.test_type.R: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env Rscript 2 | 3 | library(testthat) 4 | 5 | context("type") 6 | 7 | test_that("test_set", { 8 | s <- thriftr::t_load("type.thrift") 9 | expect_that(s$thrift_file, equals('type.thrift')) 10 | expect_that(names(s$thrift_meta), equals('structs')) 11 | expect_that(s$Set$thrift_spec[["1"]][[1]], equals(thriftr::TType$SET)) 12 | expect_that(s$Set$thrift_spec[["1"]][[2]], equals("a_set")) 13 | expect_that(s$Set$thrift_spec[["1"]][[3]], equals(thriftr::TType$STRING)) 14 | expect_that(s$Set$thrift_spec[["1"]][[4]], equals(TRUE)) 15 | expect_that(s$Set$new()$a_set, equals(NA)) 16 | expect_that(s$Set$new(list('abc'))$a_set, equals(list('abc'))) 17 | }) 18 | -------------------------------------------------------------------------------- /tests/testthat/test.test_type_mismatch.R: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env Rscript 2 | 3 | library(testthat) 4 | 5 | context("type_mismatch") 6 | 7 | Struct <- thriftr::to_proper_struct( 8 | list( 9 | list("1", list(thriftr::TType$I32, 'a', FALSE)), 10 | list("2", list(thriftr::TType$STRING, 'b', FALSE)), 11 | list("3", list(thriftr::TType$DOUBLE, 'c', FALSE)) 12 | ), 13 | list( 14 | list('a', NA), 15 | list('b', NA), 16 | list('c', NA) 17 | ) 18 | ) 19 | 20 | TItem <- thriftr::to_proper_struct( 21 | list( 22 | list("1", list(thriftr::TType$I32, 'id', FALSE)), 23 | list("2", list(thriftr::TType$LIST, 'phones', thriftr::TType$STRING, FALSE)), 24 | list("3", list(thriftr::TType$MAP, 'addr', list(thriftr::TType$I32, thriftr::TType$STRING), FALSE)), 25 | list("4", list(thriftr::TType$LIST, 'data', list(thriftr::TType$STRUCT, Struct), FALSE)) 26 | ), 27 | list( 28 | list('id', NA), 29 | list('phones', NA), 30 | list('addr', NA), 31 | list('data', NA) 32 | ) 33 | ) 34 | 35 | test_that("test_list_type_mismatch", { 36 | TMismatchItem <- thriftr::to_proper_struct( 37 | list( 38 | list("1", list(thriftr::TType$I32, 'id', FALSE)), 39 | list("2", list(thriftr::TType$LIST, 'phones', list(thriftr::TType$I32, FALSE), FALSE)) 40 | ), 41 | list( 42 | list('id', NA), 43 | list('phones', NA) 44 | ) 45 | ) 46 | 47 | t <- thriftr::TMemoryBuffer$new() 48 | p <- thriftr::TBinaryProtocol$new(t) 49 | 50 | item <- TItem$new(id=37, phones=list("23424", "235125")) 51 | p$write_struct(item) 52 | p$write_message_end() 53 | 54 | item2 <- TMismatchItem$new() 55 | p$read_struct(item2) 56 | 57 | expect_equal(length(item2$phones), 0) 58 | }) 59 | 60 | test_that("test_map_type_mismatch", { 61 | TMismatchItem <- thriftr::to_proper_struct( 62 | list( 63 | list("1", list(thriftr::TType$I32, 'id', FALSE)), 64 | list("3", list(thriftr::TType$MAP, 'addr', list(thriftr::TType$STRING, thriftr::TType$STRING), FALSE)) 65 | ), 66 | list( 67 | list('id', NA), 68 | list('addr', NA) 69 | ) 70 | ) 71 | 72 | t <- thriftr::TMemoryBuffer$new() 73 | p <- thriftr::TBinaryProtocol$new(t) 74 | 75 | addr <- new.env() 76 | addr[["1"]] <- "hello" 77 | addr[["2"]] <- "world" 78 | item <- TItem$new(id=37, addr=addr) 79 | 80 | p$write_struct(item) 81 | p$write_message_end() 82 | 83 | item2 <- TMismatchItem$new() 84 | p$read_struct(item2) 85 | 86 | expect_equal(length(names(item2$addr)), 0) 87 | }) 88 | 89 | test_that("test_struct_mismatch", { 90 | MismatchStruct <- thriftr::to_proper_struct( 91 | list( 92 | list("1", list(thriftr::TType$STRING, 'a', FALSE)), 93 | list("2", list(thriftr::TType$STRING, 'b', FALSE)) 94 | ), 95 | list( 96 | list('a', NA), 97 | list('b', NA) 98 | ) 99 | ) 100 | 101 | TMismatchItem <- thriftr::to_proper_struct( 102 | list( 103 | list("1", list(thriftr::TType$I32, 'id', FALSE)), 104 | list("2", list(thriftr::TType$LIST, 'phones', FALSE)), 105 | list("3", list(thriftr::TType$MAP, list(thriftr::TType$I32, thriftr::TType$STRING), 'addr', FALSE)), 106 | list("4", list(thriftr::TType$LIST, 'data', list(thriftr::TType$STRUCT, MismatchStruct), FALSE)) 107 | ), 108 | list( 109 | list('id', NA), 110 | list('phones', NA), 111 | list('addr', NA) 112 | ) 113 | ) 114 | 115 | t <- thriftr::TMemoryBuffer$new() 116 | p <- thriftr::TBinaryProtocol$new(t) 117 | 118 | item <- TItem$new(id=37, data=list( 119 | Struct$new(a=1, b="hello", c=0.123), 120 | Struct$new(a=2, b="world", c=34.342346), 121 | Struct$new(a=3, b="when", c=25235.14) 122 | )) 123 | 124 | p$write_struct(item) 125 | p$write_message_end() 126 | 127 | item2 <- TMismatchItem$new() 128 | p$read_struct(item2) 129 | 130 | expect_equal(length(item2$data), 3) 131 | for (i in item2$data) { 132 | expect_false(is.na(i$b)) 133 | } 134 | }) 135 | -------------------------------------------------------------------------------- /tests/testthat/type.thrift: -------------------------------------------------------------------------------- 1 | struct Set { 2 | 1: required set a_set 3 | } 4 | --------------------------------------------------------------------------------