Skip to content

Commit 6969a6f

Browse files
authored
Merge pull request #223 from konard/issue-206-9d24f61cc2c2
feat: поддержка RFC 6901 (JSON Pointer) для путей — escaping ~/slash в ключах (Этап 10.2, Issue #206)
2 parents ef83101 + 31ef752 commit 6969a6f

6 files changed

Lines changed: 310 additions & 21 deletions

File tree

pjson_db_helpers.h

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,44 @@
1414
// Вспомогательные функции: путевая адресация
1515
// ===========================================================================
1616

17+
/// Декодировать сегмент пути по RFC 6901 (JSON Pointer):
18+
/// ~1 → /
19+
/// ~0 → ~
20+
/// Порядок декодирования важен: сначала ~1, затем ~0.
21+
inline std::string pjson_decode_rfc6901_segment( const char* data, uintptr_t len )
22+
{
23+
std::string result;
24+
result.reserve( len );
25+
for ( uintptr_t i = 0; i < len; ++i )
26+
{
27+
if ( data[i] == '~' && i + 1 < len )
28+
{
29+
if ( data[i + 1] == '1' )
30+
{
31+
result += '/';
32+
++i;
33+
continue;
34+
}
35+
else if ( data[i + 1] == '0' )
36+
{
37+
result += '~';
38+
++i;
39+
continue;
40+
}
41+
}
42+
result += data[i];
43+
}
44+
return result;
45+
}
46+
47+
/// Декодировать сегмент пути по RFC 6901 (JSON Pointer) из std::string.
48+
inline std::string pjson_decode_rfc6901_segment( const std::string& seg )
49+
{
50+
return pjson_decode_rfc6901_segment( seg.c_str(), static_cast<uintptr_t>( seg.size() ) );
51+
}
52+
1753
/// Разбить путь на родительский путь и последний сегмент.
54+
/// Последний сегмент декодируется по RFC 6901.
1855
inline void pjson_split_path( const char* path, std::string& parent, std::string& last )
1956
{
2057
if ( path == nullptr || path[0] == '\0' )
@@ -33,17 +70,17 @@ inline void pjson_split_path( const char* path, std::string& parent, std::string
3370
if ( pos == std::string::npos )
3471
{
3572
parent = "";
36-
last = full;
73+
last = pjson_decode_rfc6901_segment( full );
3774
}
3875
else if ( pos == 0 )
3976
{
4077
parent = "/";
41-
last = full.substr( 1 );
78+
last = pjson_decode_rfc6901_segment( full.substr( 1 ) );
4279
}
4380
else
4481
{
4582
parent = full.substr( 0, pos );
46-
last = full.substr( pos + 1 );
83+
last = pjson_decode_rfc6901_segment( full.substr( pos + 1 ) );
4784
}
4885
}
4986

pjson_db_pmm.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ class pjson_db_pmm
627627
if ( seg_len == 0 )
628628
continue;
629629

630-
std::string seg( seg_start, seg_len );
630+
std::string seg = pjson_decode_rfc6901_segment( seg_start, seg_len );
631631

632632
// --- разыменование $ref ---
633633
if ( deref_refs )

plan.md

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
| 7. Унификация итераторов | CRTP-база pjson_iterator_base + шаблонный pjson_range; ~22 строки удалено ||
2222

2323
**Итого удалено:** ~5 файлов (~1900 строк), ~381 строка дублирования.
24-
**Тесты:** 659 тестов, ~360 000 assertion.
24+
**Тесты:** 676 тестов, ~360 000 assertion.
2525

2626
---
2727

@@ -70,18 +70,9 @@
7070

7171
---
7272

73-
### Проблема 7: Отсутствие escaping '/' в path-сегментах
73+
### ~~Проблема 7: Отсутствие escaping '/' в path-сегментах~~
7474

75-
**Файл:** `pjson_db_pmm.h`, метод `_walk_path()`
76-
77-
Символ `/` используется как разделитель пути, но не поддерживается экранирование. Если ключ объекта содержит `/`, обратиться к нему через path-адресацию невозможно:
78-
79-
```cpp
80-
db.put("/config", R"({"a/b": 42})");
81-
db.get("/config/a/b"); // ищет config → "a" → "b" вместо config → "a/b"
82-
```
83-
84-
**Решение:** Поддержать RFC 6901 (JSON Pointer): символ `~` экранируется как `~0`, `/` как `~1`.
75+
**Решено в Этапе 10.2:** Реализована поддержка RFC 6901 (JSON Pointer) для путей. Добавлена функция `pjson_decode_rfc6901_segment()` в `pjson_db_helpers.h`, которая декодирует `~1``/` и `~0``~`. Декодирование применяется в `_walk_path()` и `pjson_split_path()`. Ключи, содержащие `/` и `~`, теперь доступны через path-адресацию с экранированием. Добавлены 17 тестов.
8576

8677
---
8778

@@ -190,7 +181,7 @@ pvector был бы предпочтительнее **только** при ч
190181
| # | Проблема | Файл | Сложность | Влияние |
191182
|---|----------|------|-----------|---------|
192183
| ~~3~~ | ~~Глобальное состояние PMM (Этап A)~~ | ~~pam_pmm.h~~ | ~~Высокая~~ | ✅ |
193-
| 7 | Нет escaping '/' в путях | pjson_db_pmm.h | Средняя | Совместимость |
184+
| ~~7~~ | ~~Нет escaping '/' в путях~~ | ~~pjson_db_pmm.h~~ | ~~Средняя~~ | |
194185
| 10 | Многократный resolve в is_*() | pjson_node.h | Средняя | Производительность |
195186
| 11 | const-корректность _walk_path | pjson_db_pmm.h | Средняя | Корректность |
196187
@@ -215,7 +206,7 @@ pvector был бы предпочтительнее **только** при ч
215206

216207
Этап 10: Приоритет 3 — архитектурные улучшения
217208
10.1 ✅ Инкапсуляция глобального состояния pam_pmm (Этап A: структура pam_pmm_state + синглтон)
218-
10.2 Поддержка RFC 6901 (JSON Pointer) для путей
209+
10.2 Поддержка RFC 6901 (JSON Pointer) для путей
219210
10.3 Оптимизация tag-проверок на горячих путях
220211
10.4 const-корректность с явной передачей состояния
221212
```
@@ -226,6 +217,7 @@ pvector был бы предпочтительнее **только** при ч
226217
227218
| Дата | Изменение |
228219
|------|-----------|
220+
| 2026-03-22 | Этап 10.2: поддержка RFC 6901 (JSON Pointer) для путей — escaping ~/slash в ключах (Issue #206) |
229221
| 2026-03-22 | Этап 10.1: инкапсуляция глобального состояния pam_pmm в структуру pam_pmm_state (Issue #205) |
230222
| 2026-03-22 | Этап 9.4: parse_object() в один проход без двойного парсинга (Issue #192) |
231223
| 2026-03-22 | Этап 9.3: _free_node_tree и _resolve_refs_in_subtree через pjson_traverse_subtree с visitor-функторами (Issue #191) |

readme.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ int main() {
5151
| **Два типа строк** | readonly (`pstringview_pmm`): ключи объектов, пути `$ref`, интернированы, сравнение O(1); readwrite (`PamManager::pstring`): строковые значения JSON, изменяемые на лету |
5252
| **Нет SSO** | Ни `pstringview_pmm`, ни `PamManager::pstring` не используют SSO — все строки хранятся в ПАП (необходимо для сквозного поиска) |
5353
| **jsonRVM-совместимость** | `pstring`-узлы могут модифицироваться непосредственно в БД библиотекой [jsonRVM](https://github.com/netkeep80/jsonRVM); `node_id`-ссылки стабильны при resize array/object |
54-
| **Path-адресация** | Доступ к узлам через строковые пути вида `/a/b/0/c` |
54+
| **Path-адресация** | Доступ к узлам через строковые пути вида `/a/b/0/c`; RFC 6901 escaping (`~1` для `/`, `~0` для `~`) |
5555
| **$ref как указатели** | `{ "$ref": "/path" }` при разборе становится прямым указателем в ПАП |
5656
| **Метрики** | Персистная структура `db_metrics_pmm` в ПАМ; обновляется при каждой мутации; доступ через `/$metrics/...` |
5757
| **pmap-интерфейс** | `operator[]`, `find`, `insert` для доступа по пути без явного указания типа |
@@ -139,7 +139,7 @@ int main() {
139139
| `pjson_db_pmm.h` | D | Менеджер персистной JSON-БД: path-адресация, `put`/`get`/`erase`, `$ref`, метрики, поиск, клонирование |
140140
| `deps/pmm/pmm.h` | A | [PersistMemoryManager](https://github.com/netkeep80/PersistMemoryManager) — бэкенд ПАП |
141141
| `main.cpp` || Демонстрационная программа |
142-
| `tests/` || Тесты на Catch2 (659 тестов, ~360 000 assertion) |
142+
| `tests/` || Тесты на Catch2 (676 тестов, ~360 000 assertion) |
143143
| `CMakeLists.txt` || Система сборки (CMake 3.16+, C++20) |
144144

145145
---
@@ -195,6 +195,19 @@ node_view age = db.get("/users/alice/age");
195195
// age.as_int() -> 30
196196
```
197197

198+
### RFC 6901 — ключи с `/` и `~`
199+
200+
```cpp
201+
// Ключ "a/b" экранируется как "a~1b" в пути (RFC 6901 JSON Pointer)
202+
db.put("/config/a~1b", 42);
203+
node_view v = db.get("/config/a~1b");
204+
// v.as_int() -> 42; фактический ключ в объекте — "a/b"
205+
206+
// Ключ "x~y" экранируется как "x~0y"
207+
db.put("/data/x~0y", "hello");
208+
// фактический ключ — "x~y"
209+
```
210+
198211
### Работа с `$ref`
199212

200213
```cpp
@@ -439,7 +452,7 @@ db.put("/copy/name", "Bob");
439452
## Известные ограничения
440453

441454
- **Глобальное состояние PMM** — в одном процессе может быть открыта только одна БД (см. [plan.md](plan.md), Проблема 3); состояние инкапсулировано в `pam_pmm_state`, передача как параметра — в будущих версиях
442-
- **Нет escaping `/` в путях**ключи объектов, содержащие `/`, недоступны через path-адресацию (см. [plan.md](plan.md), Проблема 7)
455+
- ~~**Нет escaping `/` в путях**~~**Исправлено** в Этапе 10.2: поддержка RFC 6901 (JSON Pointer) — `~1` для `/`, `~0` для `~` в сегментах путей
443456
- ~~**Утечка временных узлов метрик**~~**Исправлено** в Этапе 8.4: один pre-allocated узел переиспользуется для всех вызовов метрик
444457
- **Не потокобезопасно** — CacheManagerConfig (по умолчанию) использует NoLock; для многопоточности нужен PersistentDataConfig
445458
- **Строки не освобождаются** — словарь `pstringview_pmm` только растёт

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ set(TEST_SOURCES
3838
test_regression_pmm_types.cpp
3939
test_compat_save_load.cpp
4040
test_pjson_ref_stability.cpp
41+
test_pjson_rfc6901.cpp
4142
)
4243

4344
add_executable(tests ${TEST_SOURCES})

0 commit comments

Comments
 (0)