/
codespawn
/
tgstream
Обзор
Документация
Войти
/
codespawn
/
tgstream
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
16
CI/CD
Аналитика
Безопасность
master
internal/adapter/httpserver/format.go
77 строк
3 KB
codespawn
feat: first working version
27 июн 2026, 17:00
27 июн 2026, 17:00
492bb24
Код
Авторство
О чём код?
package httpserver import ( "strings" "gitverse.ru/codespawn/tgstream/internal/port" ) // formatterRegistry — реестр Formatter'ов для content negotiation // (PROTOCOL.md §B.6). Дефолт — первый зарегистрированный (MVP: RSS). type formatterRegistry struct { def port.Formatter byKey map[string]port.Formatter // "rss" → Formatter byAccept map[string]string // media-type → key ("application/rss+xml" → "rss") } func newFormatterRegistry(formatters []NamedFormatter) formatterRegistry { if len(formatters) == 0 { panic("httpserver: at least one Formatter required") } r := formatterRegistry{ def: formatters[0].Formatter, byKey: make(map[string]port.Formatter, len(formatters)), byAccept: make(map[string]string, len(formatters)), } for _, nf := range formatters { r.byKey[nf.Key] = nf.Formatter // Карта media-type → ключ (по ContentType форматера). if nf.Formatter != nil { r.byAccept[nf.Formatter.ContentType()] = nf.Key } } return r } // selectFormatter выбирает Formatter по приоритету (PROTOCOL.md §B.6): // ?format= > суффикс пути > Accept > дефолт. Незарегистрированный запрос // молча падает на дефолт (MVP-поведение: RSS доступен всегда). func (r formatterRegistry) selectFormatter(suffixKey, formatParam, accept string) port.Formatter { // 1. ?format= — самый явный. if f, ok := r.byKey[strings.ToLower(strings.TrimSpace(formatParam))]; ok { return f } // 2. суффикс пути (.atom/.json/.rss). if f, ok := r.byKey[suffixKey]; ok { return f } // 3. Accept — простое сопоставление по content-type (без парсинга q-values). if key, ok := matchAccept(r.byAccept, accept); ok { return r.byKey[key] } // 4. дефолт. return r.def } // matchAccept ищет в Accept первый известный media-type. Приоритет: специфичные // типы (atom/json) проверяются раньше rss/xml/wildcard, чтобы точные запросы // выигрывали у общего "Accept: */*". func matchAccept(byAccept map[string]string, accept string) (string, bool) { accept = strings.ToLower(accept) // Сначала специфичные (не-rss) форматы. for _, mt := range []string{"application/atom+xml", "application/feed+json"} { if key, ok := byAccept[mt]; ok && strings.Contains(accept, mt) { return key, true } } // RSS / XML / любой (включая */*) → rss-дефолт через явный тип. if strings.Contains(accept, "application/rss+xml") || strings.Contains(accept, "application/xml") || strings.Contains(accept, "text/xml") || strings.Contains(accept, "*/*") { if key, ok := byAccept["application/rss+xml"]; ok { return key, true } } return "", false }