1/*
2 * Copyright 2011-present Facebook, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <folly/json_pointer.h>
18
19#include <folly/String.h>
20
21namespace folly {
22
23// static, public
24Expected<json_pointer, json_pointer::parse_error> json_pointer::try_parse(
25 StringPiece const str) {
26 // pointer describes complete document
27 if (str.empty()) {
28 return json_pointer{};
29 }
30
31 if (str.at(0) != '/') {
32 return makeUnexpected(parse_error::invalid_first_character);
33 }
34
35 std::vector<std::string> tokens;
36 splitTo<std::string>("/", str, std::inserter(tokens, tokens.begin()));
37 tokens.erase(tokens.begin());
38
39 for (auto& token : tokens) {
40 if (!unescape(token)) {
41 return makeUnexpected(parse_error::invalid_escape_sequence);
42 }
43 }
44
45 return json_pointer(std::move(tokens));
46}
47
48// static, public
49json_pointer json_pointer::parse(StringPiece const str) {
50 auto res = try_parse(str);
51 if (res.hasValue()) {
52 return std::move(res.value());
53 }
54 switch (res.error()) {
55 case parse_error::invalid_first_character:
56 throw json_pointer::parse_exception(
57 "non-empty JSON pointer string does not start with '/'");
58 case parse_error::invalid_escape_sequence:
59 throw json_pointer::parse_exception(
60 "Invalid escape sequence in JSON pointer string");
61 default:
62 assume_unreachable();
63 }
64}
65
66bool json_pointer::is_prefix_of(json_pointer const& other) const noexcept {
67 auto const& other_tokens = other.tokens();
68 if (tokens_.size() > other_tokens.size()) {
69 return false;
70 }
71 auto const other_begin = other_tokens.cbegin();
72 auto const other_end = other_tokens.cbegin() + tokens_.size();
73 return std::equal(tokens_.cbegin(), tokens_.cend(), other_begin, other_end);
74}
75
76std::vector<std::string> const& json_pointer::tokens() const {
77 return tokens_;
78}
79
80// private
81json_pointer::json_pointer(std::vector<std::string> tokens) noexcept
82 : tokens_{std::move(tokens)} {}
83
84// private, static
85bool json_pointer::unescape(std::string& str) {
86 char const* end = &str[str.size()];
87 char* out = &str.front();
88 char const* decode = out;
89 while (decode < end) {
90 if (*decode != '~') {
91 *out++ = *decode++;
92 continue;
93 }
94 if (decode + 1 == end) {
95 return false;
96 }
97 switch (decode[1]) {
98 case '1':
99 *out++ = '/';
100 break;
101 case '0':
102 *out++ = '~';
103 break;
104 default:
105 return false;
106 }
107 decode += 2;
108 }
109 str.resize(out - &str.front());
110 return true;
111}
112
113} // namespace folly
114