encode.ex (21380B)
1 defmodule Jason.EncodeError do 2 defexception [:message] 3 4 @type t :: %__MODULE__{message: String.t} 5 6 def new({:duplicate_key, key}) do 7 %__MODULE__{message: "duplicate key: #{key}"} 8 end 9 def new({:invalid_byte, byte, original}) do 10 %__MODULE__{message: "invalid byte #{inspect byte, base: :hex} in #{inspect original}"} 11 end 12 end 13 14 defmodule Jason.Encode do 15 @moduledoc """ 16 Utilities for encoding elixir values to JSON. 17 """ 18 19 import Bitwise 20 21 alias Jason.{Codegen, EncodeError, Encoder, Fragment, OrderedObject} 22 23 @typep escape :: (String.t, String.t, integer -> iodata) 24 @typep encode_map :: (map, escape, encode_map -> iodata) 25 @opaque opts :: {escape, encode_map} 26 27 @dialyzer :no_improper_lists 28 29 # @compile :native 30 31 @doc false 32 @spec encode(any, map) :: {:ok, iodata} | {:error, EncodeError.t | Exception.t} 33 def encode(value, opts) do 34 escape = escape_function(opts) 35 encode_map = encode_map_function(opts) 36 try do 37 {:ok, value(value, escape, encode_map)} 38 catch 39 :throw, %EncodeError{} = e -> 40 {:error, e} 41 :error, %Protocol.UndefinedError{protocol: Jason.Encoder} = e -> 42 {:error, e} 43 end 44 end 45 46 defp encode_map_function(%{maps: maps}) do 47 case maps do 48 :naive -> &map_naive/3 49 :strict -> &map_strict/3 50 end 51 end 52 53 defp escape_function(%{escape: escape}) do 54 case escape do 55 :json -> &escape_json/3 56 :html_safe -> &escape_html/3 57 :unicode_safe -> &escape_unicode/3 58 :javascript_safe -> &escape_javascript/3 59 # Keep for compatibility with Poison 60 :javascript -> &escape_javascript/3 61 :unicode -> &escape_unicode/3 62 end 63 end 64 65 @doc """ 66 Equivalent to calling the `Jason.Encoder.encode/2` protocol function. 67 68 Slightly more efficient for built-in types because of the internal dispatching. 69 """ 70 @spec value(term, opts) :: iodata 71 def value(value, {escape, encode_map}) do 72 value(value, escape, encode_map) 73 end 74 75 @doc false 76 # We use this directly in the helpers and deriving for extra speed 77 def value(value, escape, _encode_map) when is_atom(value) do 78 encode_atom(value, escape) 79 end 80 81 def value(value, escape, _encode_map) when is_binary(value) do 82 encode_string(value, escape) 83 end 84 85 def value(value, _escape, _encode_map) when is_integer(value) do 86 integer(value) 87 end 88 89 def value(value, _escape, _encode_map) when is_float(value) do 90 float(value) 91 end 92 93 def value(value, escape, encode_map) when is_list(value) do 94 list(value, escape, encode_map) 95 end 96 97 def value(%{__struct__: module} = value, escape, encode_map) do 98 struct(value, escape, encode_map, module) 99 end 100 101 def value(value, escape, encode_map) when is_map(value) do 102 case Map.to_list(value) do 103 [] -> "{}" 104 keyword -> encode_map.(keyword, escape, encode_map) 105 end 106 end 107 108 def value(value, escape, encode_map) do 109 Encoder.encode(value, {escape, encode_map}) 110 end 111 112 @compile {:inline, integer: 1, float: 1} 113 114 @spec atom(atom, opts) :: iodata 115 def atom(atom, {escape, _encode_map}) do 116 encode_atom(atom, escape) 117 end 118 119 defp encode_atom(nil, _escape), do: "null" 120 defp encode_atom(true, _escape), do: "true" 121 defp encode_atom(false, _escape), do: "false" 122 defp encode_atom(atom, escape), 123 do: encode_string(Atom.to_string(atom), escape) 124 125 @spec integer(integer) :: iodata 126 def integer(integer) do 127 Integer.to_string(integer) 128 end 129 130 has_short_format = try do 131 :erlang.float_to_binary(1.0, [:short]) 132 catch 133 _, _ -> false 134 else 135 _ -> true 136 end 137 138 @spec float(float) :: iodata 139 if has_short_format do 140 def float(float) do 141 :erlang.float_to_binary(float, [:short]) 142 end 143 else 144 def float(float) do 145 :io_lib_format.fwrite_g(float) 146 end 147 end 148 149 @spec list(list, opts) :: iodata 150 def list(list, {escape, encode_map}) do 151 list(list, escape, encode_map) 152 end 153 154 defp list([], _escape, _encode_map) do 155 "[]" 156 end 157 158 defp list([head | tail], escape, encode_map) do 159 [?[, value(head, escape, encode_map) 160 | list_loop(tail, escape, encode_map)] 161 end 162 163 defp list_loop([], _escape, _encode_map) do 164 ']' 165 end 166 167 defp list_loop([head | tail], escape, encode_map) do 168 [?,, value(head, escape, encode_map) 169 | list_loop(tail, escape, encode_map)] 170 end 171 172 @spec keyword(keyword, opts) :: iodata 173 def keyword(list, _) when list == [], do: "{}" 174 def keyword(list, {escape, encode_map}) when is_list(list) do 175 encode_map.(list, escape, encode_map) 176 end 177 178 @spec map(map, opts) :: iodata 179 def map(value, {escape, encode_map}) do 180 case Map.to_list(value) do 181 [] -> "{}" 182 keyword -> encode_map.(keyword, escape, encode_map) 183 end 184 end 185 186 defp map_naive([{key, value} | tail], escape, encode_map) do 187 ["{\"", key(key, escape), "\":", 188 value(value, escape, encode_map) 189 | map_naive_loop(tail, escape, encode_map)] 190 end 191 192 defp map_naive_loop([], _escape, _encode_map) do 193 '}' 194 end 195 196 defp map_naive_loop([{key, value} | tail], escape, encode_map) do 197 [",\"", key(key, escape), "\":", 198 value(value, escape, encode_map) 199 | map_naive_loop(tail, escape, encode_map)] 200 end 201 202 defp map_strict([{key, value} | tail], escape, encode_map) do 203 key = IO.iodata_to_binary(key(key, escape)) 204 visited = %{key => []} 205 ["{\"", key, "\":", 206 value(value, escape, encode_map) 207 | map_strict_loop(tail, escape, encode_map, visited)] 208 end 209 210 defp map_strict_loop([], _encode_map, _escape, _visited) do 211 '}' 212 end 213 214 defp map_strict_loop([{key, value} | tail], escape, encode_map, visited) do 215 key = IO.iodata_to_binary(key(key, escape)) 216 case visited do 217 %{^key => _} -> 218 error({:duplicate_key, key}) 219 _ -> 220 visited = Map.put(visited, key, []) 221 [",\"", key, "\":", 222 value(value, escape, encode_map) 223 | map_strict_loop(tail, escape, encode_map, visited)] 224 end 225 end 226 227 @spec struct(struct, opts) :: iodata 228 def struct(%module{} = value, {escape, encode_map}) do 229 struct(value, escape, encode_map, module) 230 end 231 232 # TODO: benchmark the effect of inlining the to_iso8601 functions 233 for module <- [Date, Time, NaiveDateTime, DateTime] do 234 defp struct(value, _escape, _encode_map, unquote(module)) do 235 [?", unquote(module).to_iso8601(value), ?"] 236 end 237 end 238 239 defp struct(value, _escape, _encode_map, Decimal) do 240 # silence the xref warning 241 decimal = Decimal 242 [?", decimal.to_string(value, :normal), ?"] 243 end 244 245 defp struct(value, escape, encode_map, Fragment) do 246 %{encode: encode} = value 247 encode.({escape, encode_map}) 248 end 249 250 defp struct(value, escape, encode_map, OrderedObject) do 251 case value do 252 %{values: []} -> "{}" 253 %{values: values} -> encode_map.(values, escape, encode_map) 254 end 255 end 256 257 defp struct(value, escape, encode_map, _module) do 258 Encoder.encode(value, {escape, encode_map}) 259 end 260 261 @doc false 262 # This is used in the helpers and deriving implementation 263 def key(string, escape) when is_binary(string) do 264 escape.(string, string, 0) 265 end 266 def key(atom, escape) when is_atom(atom) do 267 string = Atom.to_string(atom) 268 escape.(string, string, 0) 269 end 270 def key(other, escape) do 271 string = String.Chars.to_string(other) 272 escape.(string, string, 0) 273 end 274 275 @spec string(String.t, opts) :: iodata 276 def string(string, {escape, _encode_map}) do 277 encode_string(string, escape) 278 end 279 280 defp encode_string(string, escape) do 281 [?", escape.(string, string, 0), ?"] 282 end 283 284 slash_escapes = Enum.zip('\b\t\n\f\r\"\\', 'btnfr"\\') 285 surogate_escapes = Enum.zip([0x2028, 0x2029], ["\\u2028", "\\u2029"]) 286 ranges = [{0x00..0x1F, :unicode} | slash_escapes] 287 html_ranges = [{0x00..0x1F, :unicode}, {?<, :unicode}, {?/, ?/} | slash_escapes] 288 escape_jt = Codegen.jump_table(html_ranges, :error) 289 290 Enum.each(escape_jt, fn 291 {byte, :unicode} -> 292 sequence = List.to_string(:io_lib.format("\\u~4.16.0B", [byte])) 293 defp escape(unquote(byte)), do: unquote(sequence) 294 {byte, char} when is_integer(char) -> 295 defp escape(unquote(byte)), do: unquote(<<?\\, char>>) 296 {byte, :error} -> 297 defp escape(unquote(byte)), do: throw(:error) 298 end) 299 300 ## regular JSON escape 301 302 json_jt = Codegen.jump_table(ranges, :chunk, 0x7F + 1) 303 304 defp escape_json(data, original, skip) do 305 escape_json(data, [], original, skip) 306 end 307 308 Enum.map(json_jt, fn 309 {byte, :chunk} -> 310 defp escape_json(<<byte, rest::bits>>, acc, original, skip) 311 when byte === unquote(byte) do 312 escape_json_chunk(rest, acc, original, skip, 1) 313 end 314 {byte, _escape} -> 315 defp escape_json(<<byte, rest::bits>>, acc, original, skip) 316 when byte === unquote(byte) do 317 acc = [acc | escape(byte)] 318 escape_json(rest, acc, original, skip + 1) 319 end 320 end) 321 defp escape_json(<<char::utf8, rest::bits>>, acc, original, skip) 322 when char <= 0x7FF do 323 escape_json_chunk(rest, acc, original, skip, 2) 324 end 325 defp escape_json(<<char::utf8, rest::bits>>, acc, original, skip) 326 when char <= 0xFFFF do 327 escape_json_chunk(rest, acc, original, skip, 3) 328 end 329 defp escape_json(<<_char::utf8, rest::bits>>, acc, original, skip) do 330 escape_json_chunk(rest, acc, original, skip, 4) 331 end 332 defp escape_json(<<>>, acc, _original, _skip) do 333 acc 334 end 335 defp escape_json(<<byte, _rest::bits>>, _acc, original, _skip) do 336 error({:invalid_byte, byte, original}) 337 end 338 339 Enum.map(json_jt, fn 340 {byte, :chunk} -> 341 defp escape_json_chunk(<<byte, rest::bits>>, acc, original, skip, len) 342 when byte === unquote(byte) do 343 escape_json_chunk(rest, acc, original, skip, len + 1) 344 end 345 {byte, _escape} -> 346 defp escape_json_chunk(<<byte, rest::bits>>, acc, original, skip, len) 347 when byte === unquote(byte) do 348 part = binary_part(original, skip, len) 349 acc = [acc, part | escape(byte)] 350 escape_json(rest, acc, original, skip + len + 1) 351 end 352 end) 353 defp escape_json_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len) 354 when char <= 0x7FF do 355 escape_json_chunk(rest, acc, original, skip, len + 2) 356 end 357 defp escape_json_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len) 358 when char <= 0xFFFF do 359 escape_json_chunk(rest, acc, original, skip, len + 3) 360 end 361 defp escape_json_chunk(<<_char::utf8, rest::bits>>, acc, original, skip, len) do 362 escape_json_chunk(rest, acc, original, skip, len + 4) 363 end 364 defp escape_json_chunk(<<>>, acc, original, skip, len) do 365 part = binary_part(original, skip, len) 366 [acc | part] 367 end 368 defp escape_json_chunk(<<byte, _rest::bits>>, _acc, original, _skip, _len) do 369 error({:invalid_byte, byte, original}) 370 end 371 372 ## javascript safe JSON escape 373 374 defp escape_javascript(data, original, skip) do 375 escape_javascript(data, [], original, skip) 376 end 377 378 Enum.map(json_jt, fn 379 {byte, :chunk} -> 380 defp escape_javascript(<<byte, rest::bits>>, acc, original, skip) 381 when byte === unquote(byte) do 382 escape_javascript_chunk(rest, acc, original, skip, 1) 383 end 384 {byte, _escape} -> 385 defp escape_javascript(<<byte, rest::bits>>, acc, original, skip) 386 when byte === unquote(byte) do 387 acc = [acc | escape(byte)] 388 escape_javascript(rest, acc, original, skip + 1) 389 end 390 end) 391 defp escape_javascript(<<char::utf8, rest::bits>>, acc, original, skip) 392 when char <= 0x7FF do 393 escape_javascript_chunk(rest, acc, original, skip, 2) 394 end 395 Enum.map(surogate_escapes, fn {byte, escape} -> 396 defp escape_javascript(<<unquote(byte)::utf8, rest::bits>>, acc, original, skip) do 397 acc = [acc | unquote(escape)] 398 escape_javascript(rest, acc, original, skip + 3) 399 end 400 end) 401 defp escape_javascript(<<char::utf8, rest::bits>>, acc, original, skip) 402 when char <= 0xFFFF do 403 escape_javascript_chunk(rest, acc, original, skip, 3) 404 end 405 defp escape_javascript(<<_char::utf8, rest::bits>>, acc, original, skip) do 406 escape_javascript_chunk(rest, acc, original, skip, 4) 407 end 408 defp escape_javascript(<<>>, acc, _original, _skip) do 409 acc 410 end 411 defp escape_javascript(<<byte, _rest::bits>>, _acc, original, _skip) do 412 error({:invalid_byte, byte, original}) 413 end 414 415 Enum.map(json_jt, fn 416 {byte, :chunk} -> 417 defp escape_javascript_chunk(<<byte, rest::bits>>, acc, original, skip, len) 418 when byte === unquote(byte) do 419 escape_javascript_chunk(rest, acc, original, skip, len + 1) 420 end 421 {byte, _escape} -> 422 defp escape_javascript_chunk(<<byte, rest::bits>>, acc, original, skip, len) 423 when byte === unquote(byte) do 424 part = binary_part(original, skip, len) 425 acc = [acc, part | escape(byte)] 426 escape_javascript(rest, acc, original, skip + len + 1) 427 end 428 end) 429 defp escape_javascript_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len) 430 when char <= 0x7FF do 431 escape_javascript_chunk(rest, acc, original, skip, len + 2) 432 end 433 Enum.map(surogate_escapes, fn {byte, escape} -> 434 defp escape_javascript_chunk(<<unquote(byte)::utf8, rest::bits>>, acc, original, skip, len) do 435 part = binary_part(original, skip, len) 436 acc = [acc, part | unquote(escape)] 437 escape_javascript(rest, acc, original, skip + len + 3) 438 end 439 end) 440 defp escape_javascript_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len) 441 when char <= 0xFFFF do 442 escape_javascript_chunk(rest, acc, original, skip, len + 3) 443 end 444 defp escape_javascript_chunk(<<_char::utf8, rest::bits>>, acc, original, skip, len) do 445 escape_javascript_chunk(rest, acc, original, skip, len + 4) 446 end 447 defp escape_javascript_chunk(<<>>, acc, original, skip, len) do 448 part = binary_part(original, skip, len) 449 [acc | part] 450 end 451 defp escape_javascript_chunk(<<byte, _rest::bits>>, _acc, original, _skip, _len) do 452 error({:invalid_byte, byte, original}) 453 end 454 455 ## HTML safe JSON escape 456 457 html_jt = Codegen.jump_table(html_ranges, :chunk, 0x7F + 1) 458 459 defp escape_html(data, original, skip) do 460 escape_html(data, [], original, skip) 461 end 462 463 Enum.map(html_jt, fn 464 {byte, :chunk} -> 465 defp escape_html(<<byte, rest::bits>>, acc, original, skip) 466 when byte === unquote(byte) do 467 escape_html_chunk(rest, acc, original, skip, 1) 468 end 469 {byte, _escape} -> 470 defp escape_html(<<byte, rest::bits>>, acc, original, skip) 471 when byte === unquote(byte) do 472 acc = [acc | escape(byte)] 473 escape_html(rest, acc, original, skip + 1) 474 end 475 end) 476 defp escape_html(<<char::utf8, rest::bits>>, acc, original, skip) 477 when char <= 0x7FF do 478 escape_html_chunk(rest, acc, original, skip, 2) 479 end 480 Enum.map(surogate_escapes, fn {byte, escape} -> 481 defp escape_html(<<unquote(byte)::utf8, rest::bits>>, acc, original, skip) do 482 acc = [acc | unquote(escape)] 483 escape_html(rest, acc, original, skip + 3) 484 end 485 end) 486 defp escape_html(<<char::utf8, rest::bits>>, acc, original, skip) 487 when char <= 0xFFFF do 488 escape_html_chunk(rest, acc, original, skip, 3) 489 end 490 defp escape_html(<<_char::utf8, rest::bits>>, acc, original, skip) do 491 escape_html_chunk(rest, acc, original, skip, 4) 492 end 493 defp escape_html(<<>>, acc, _original, _skip) do 494 acc 495 end 496 defp escape_html(<<byte, _rest::bits>>, _acc, original, _skip) do 497 error({:invalid_byte, byte, original}) 498 end 499 500 Enum.map(html_jt, fn 501 {byte, :chunk} -> 502 defp escape_html_chunk(<<byte, rest::bits>>, acc, original, skip, len) 503 when byte === unquote(byte) do 504 escape_html_chunk(rest, acc, original, skip, len + 1) 505 end 506 {byte, _escape} -> 507 defp escape_html_chunk(<<byte, rest::bits>>, acc, original, skip, len) 508 when byte === unquote(byte) do 509 part = binary_part(original, skip, len) 510 acc = [acc, part | escape(byte)] 511 escape_html(rest, acc, original, skip + len + 1) 512 end 513 end) 514 defp escape_html_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len) 515 when char <= 0x7FF do 516 escape_html_chunk(rest, acc, original, skip, len + 2) 517 end 518 Enum.map(surogate_escapes, fn {byte, escape} -> 519 defp escape_html_chunk(<<unquote(byte)::utf8, rest::bits>>, acc, original, skip, len) do 520 part = binary_part(original, skip, len) 521 acc = [acc, part | unquote(escape)] 522 escape_html(rest, acc, original, skip + len + 3) 523 end 524 end) 525 defp escape_html_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len) 526 when char <= 0xFFFF do 527 escape_html_chunk(rest, acc, original, skip, len + 3) 528 end 529 defp escape_html_chunk(<<_char::utf8, rest::bits>>, acc, original, skip, len) do 530 escape_html_chunk(rest, acc, original, skip, len + 4) 531 end 532 defp escape_html_chunk(<<>>, acc, original, skip, len) do 533 part = binary_part(original, skip, len) 534 [acc | part] 535 end 536 defp escape_html_chunk(<<byte, _rest::bits>>, _acc, original, _skip, _len) do 537 error({:invalid_byte, byte, original}) 538 end 539 540 ## unicode escape 541 542 defp escape_unicode(data, original, skip) do 543 escape_unicode(data, [], original, skip) 544 end 545 546 Enum.map(json_jt, fn 547 {byte, :chunk} -> 548 defp escape_unicode(<<byte, rest::bits>>, acc, original, skip) 549 when byte === unquote(byte) do 550 escape_unicode_chunk(rest, acc, original, skip, 1) 551 end 552 {byte, _escape} -> 553 defp escape_unicode(<<byte, rest::bits>>, acc, original, skip) 554 when byte === unquote(byte) do 555 acc = [acc | escape(byte)] 556 escape_unicode(rest, acc, original, skip + 1) 557 end 558 end) 559 defp escape_unicode(<<char::utf8, rest::bits>>, acc, original, skip) 560 when char <= 0xFF do 561 acc = [acc, "\\u00" | Integer.to_string(char, 16)] 562 escape_unicode(rest, acc, original, skip + 2) 563 end 564 defp escape_unicode(<<char::utf8, rest::bits>>, acc, original, skip) 565 when char <= 0x7FF do 566 acc = [acc, "\\u0" | Integer.to_string(char, 16)] 567 escape_unicode(rest, acc, original, skip + 2) 568 end 569 defp escape_unicode(<<char::utf8, rest::bits>>, acc, original, skip) 570 when char <= 0xFFF do 571 acc = [acc, "\\u0" | Integer.to_string(char, 16)] 572 escape_unicode(rest, acc, original, skip + 3) 573 end 574 defp escape_unicode(<<char::utf8, rest::bits>>, acc, original, skip) 575 when char <= 0xFFFF do 576 acc = [acc, "\\u" | Integer.to_string(char, 16)] 577 escape_unicode(rest, acc, original, skip + 3) 578 end 579 defp escape_unicode(<<char::utf8, rest::bits>>, acc, original, skip) do 580 char = char - 0x10000 581 acc = 582 [ 583 acc, 584 "\\uD", Integer.to_string(0x800 ||| (char >>> 10), 16), 585 "\\uD" | Integer.to_string(0xC00 ||| (char &&& 0x3FF), 16) 586 ] 587 escape_unicode(rest, acc, original, skip + 4) 588 end 589 defp escape_unicode(<<>>, acc, _original, _skip) do 590 acc 591 end 592 defp escape_unicode(<<byte, _rest::bits>>, _acc, original, _skip) do 593 error({:invalid_byte, byte, original}) 594 end 595 596 Enum.map(json_jt, fn 597 {byte, :chunk} -> 598 defp escape_unicode_chunk(<<byte, rest::bits>>, acc, original, skip, len) 599 when byte === unquote(byte) do 600 escape_unicode_chunk(rest, acc, original, skip, len + 1) 601 end 602 {byte, _escape} -> 603 defp escape_unicode_chunk(<<byte, rest::bits>>, acc, original, skip, len) 604 when byte === unquote(byte) do 605 part = binary_part(original, skip, len) 606 acc = [acc, part | escape(byte)] 607 escape_unicode(rest, acc, original, skip + len + 1) 608 end 609 end) 610 defp escape_unicode_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len) 611 when char <= 0xFF do 612 part = binary_part(original, skip, len) 613 acc = [acc, part, "\\u00" | Integer.to_string(char, 16)] 614 escape_unicode(rest, acc, original, skip + len + 2) 615 end 616 defp escape_unicode_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len) 617 when char <= 0x7FF do 618 part = binary_part(original, skip, len) 619 acc = [acc, part, "\\u0" | Integer.to_string(char, 16)] 620 escape_unicode(rest, acc, original, skip + len + 2) 621 end 622 defp escape_unicode_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len) 623 when char <= 0xFFF do 624 part = binary_part(original, skip, len) 625 acc = [acc, part, "\\u0" | Integer.to_string(char, 16)] 626 escape_unicode(rest, acc, original, skip + len + 3) 627 end 628 defp escape_unicode_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len) 629 when char <= 0xFFFF do 630 part = binary_part(original, skip, len) 631 acc = [acc, part, "\\u" | Integer.to_string(char, 16)] 632 escape_unicode(rest, acc, original, skip + len + 3) 633 end 634 defp escape_unicode_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len) do 635 char = char - 0x10000 636 part = binary_part(original, skip, len) 637 acc = 638 [ 639 acc, part, 640 "\\uD", Integer.to_string(0x800 ||| (char >>> 10), 16), 641 "\\uD" | Integer.to_string(0xC00 ||| (char &&& 0x3FF), 16) 642 ] 643 escape_unicode(rest, acc, original, skip + len + 4) 644 end 645 defp escape_unicode_chunk(<<>>, acc, original, skip, len) do 646 part = binary_part(original, skip, len) 647 [acc | part] 648 end 649 defp escape_unicode_chunk(<<byte, _rest::bits>>, _acc, original, _skip, _len) do 650 error({:invalid_byte, byte, original}) 651 end 652 653 @compile {:inline, error: 1} 654 defp error(error) do 655 throw EncodeError.new(error) 656 end 657 end