Утро в Библиотеке тихое, как всегда. Рыдкулл стоит у двери и делает вид, что зашёл за книгой про кабанов. Сундук встал у твоего стула и не шевелит ни одной ножкой. Из-за дальней полки торчит мятая шляпа с надписью «ВАЛШЕБНИК».
За десять карточек протокол научился многому: коды ставней, кадр с провода, сообщения по тегам, каналы, таблица обработчиков, ленивые задачи, коды башен, отправители и получатели, номера, которые не подделать, свои тесты на типы. Но башня до сих пор не сказала им ни слова. Чтобы T-1 заговорила, протоколу не хватает последнего: собрать сообщение в конверт, написать его на провод и прочитать обратно то, что пришло с Магистрали.
Что проверяет Библиотекарь
Проверяется всё, что протокол умеет с первого этапа: тесты накопительные. Сверху четыре новые вещи.
- Конверт.
Envelope<M>держит номер сообщения, маршрут и само сообщение. Маршрут собирается из двух проверенных башенTowerIdиз урока 14, номер этоMessageId. Литерал вместо номера и строка вместо башни в конверт не лягут. Тип конверта помнит, какое сообщение внутри: конверт с пингом этоEnvelope<Ping>, а не конверт вообще. - Провод.
encodeпишет конверт строкой из четырёх полей:маршрут|номер|тег|данные. Данные у каждого тега свои, у пинга их нет вовсе. - Разбор.
decodeчитает провод обратно и никогда не бросает исключений. Всё, что может пойти не так, видно в сигнатуре:Outcome<Envelope, DecodeError>. Это тот же исход, что у ленивой задачи из урока 11, только без задачи вокруг. У каждой поломки свой тег, как у сообщений в уроке 07:Garbled,BadRoute,UnknownTag. Текст сообщения может содержать|, и разбор не должен на нём споткнуться. - Доставка.
deliverразбирает провод и отдаёт сообщение таблице обработчиков из урока 10. Ошибка разбора проходит мимо обработчиков как есть.
Точный формат провода, порядок проверок в decode и содержимое каждой ошибки описаны в условии задачи. Там же примеры, на которых тесты ловят протокол.
Чек-лист перед сдачей
Пройди его сам, до того как отдать протокол Библиотекарю.
- Тесты всех прошлых этапов зелёные. Новое дописано снизу, старое не тронуто.
decode(encode(конверт))возвращает тот же конверт для каждого тега, для пустого текста и для текста с|внутри.- На мусор
decodeвозвращает ошибку. Не бросает исключение, не выдумывает пинг, не обрезает данные. - Каждая поломка названа своим тегом, и проверки идут в порядке из условия: провод, в котором испорчены и маршрут, и номер, получает одну определённую ошибку, а не какую повезёт.
- Номер в разобранном конверте это
new MessageId(...), а не объект с полями_tagиvalue. - Ни одного нового
asи ни одногоany: всё, что пришло строкой, сужается проверками из уроков 06 и 12. - Тип результата
decodeровноOutcome<Envelope, DecodeError>. Проверь его своимEqual, а не на глаз. - Сломай
decodeнарочно, например убери проверку данных уPing, и убедись, что тест покраснел. Потом верни.
Если шаг Т ругается, читай ошибку снизу вверх, как в уроке 04. Одна такая ошибка почти наверняка ждёт тебя в envelope: там бренд из урока 14 впервые попадает в шаблонный тип из урока 12.
Шифр, этап 11
Последняя карточка каталога: протокол семафора целиком. После неё башня T-1 начнёт говорить с Магистралью, и каждый её кадр можно будет собрать, прочитать и проверить, не поверив никому на слово.
Ринцвальд
Пока Библиотекарь читает, Ринцвальд выходит из-за полки и кладёт рядом свой листок.
import { type Message, ping, text } from './messages';
const decode = (wire: string): Message => {
const [, , tag, data = ''] = wire.split('|');
return tag === 'Text' ? text(data) : ping;
};
console.log(decode('T-1->T-2|1|Text|гроза у моста'));
console.log(decode('T-1->T-2|2|Text|a|b'));
console.log(decode('мусор'));
{ _tag: 'Text', body: 'гроза у моста' }
{ _tag: 'Text', body: 'a' }
{ _tag: 'Ping' }
Он прав: не падает. На первом проводе всё верно. На втором половина текста пропала молча: всё, что в тексте стояло после |, отрезано. На третьем мусор превратился в пинг, и башня, которая его получила, уверена, что сосед на связи.
Библиотекарь, не поднимая головы, тянет руку к листку Ринцвальда и стучит пальцем по сигнатуре (wire: string) => Message. «Ук». Сигнатура обещает сообщение всегда, а честного «не разобралось» в ней нет. Твоя сигнатура говорит, что разбор может не удаться, и перечисляет, как именно. Скоропись Ринцвальда не падает, она врёт. Это хуже: падение хотя бы видно.
Практика
Итог
Библиотекарь читает долго. Перелистывает протокол к началу, к таблице кодов ставней, и снова вниз, к разбору. Задерживает палец на IsNever. Рыдкулл у двери перестал делать вид, что ищет книгу. Потом Библиотекарь закрывает файл и смотрит на тебя.
Одно слово, и Пондер его не переводит. Не нужно.
Ранг: студент. Академия больше не держит тебя на пробе.
Ночью ты поднимаешься на башню T-1. Туман над рекой тот же, что в ночь, когда башня замолчала. Ты отправляешь первый кадр своим протоколом, T-1->T-2|1|Ping|, и шесть ставен щёлкают по таблице кодов из этапа 1. Через минуту с соседней башни приходит T-2->T-1|1|Ack|1. decode разбирает его без единой ошибки, deliver отдаёт обработчику подтверждений, и в журнале башни впервые с той ночи появляются две строки. Сундук за спиной тихо лязгает крышкой.
Дальше
Под утро на провод приходит кадр с Магистрали: T-7->T-1|41|Ack|40. Маршрут из настоящих башен, номер из цифр, тег известный, данные числом. Протокол принимает его без вопросов. И он врёт: T-1 за всю ночь отправила один кадр, а подтверждают ей сороковой. Разбор говорит, что кадр написан по правилам. Правда ли то, что в нём написано, разбор не знает.
Утром у ворот Академии останавливается телега с гор. С неё слезает старуха в чёрной шляпе с булавками, смотрит на башню, потом на тебя.
Следующий раздел курса это EF2 «Головология». Матушка Воскова учит отличать действие от вычисления, и начинается он с урока 17.