@@ -78,6 +78,51 @@ source_error to_source_error(rc::domain_error err) {
7878 return source_error{.kind = kind, .message = fmt::format (" {}" , err)};
7979}
8080
81+ // Narrow the open-enum source mode to Redpanda's mode. READONLY_OVERRIDE is
82+ // preserved as read_only; FORWARD and unknown have no representation and yield
83+ // nullopt, which the caller counts as a per-item error.
84+ std::optional<ppsr::mode> narrow_mode (rc::registry_mode m) {
85+ switch (m) {
86+ case rc::registry_mode::read_write:
87+ return ppsr::mode::read_write;
88+ case rc::registry_mode::read_only:
89+ case rc::registry_mode::read_only_override:
90+ return ppsr::mode::read_only;
91+ case rc::registry_mode::import :
92+ return ppsr::mode::import ;
93+ case rc::registry_mode::forward:
94+ case rc::registry_mode::unknown:
95+ return std::nullopt ;
96+ }
97+ return std::nullopt ;
98+ }
99+
100+ // Narrow the open-enum compatibility level to Redpanda's closed enum; only
101+ // unknown has no representation and yields nullopt.
102+ std::optional<ppsr::compatibility_level>
103+ narrow_compat (rc::registry_compatibility_level c) {
104+ using enum rc::registry_compatibility_level;
105+ switch (c) {
106+ case none:
107+ return ppsr::compatibility_level::none;
108+ case backward:
109+ return ppsr::compatibility_level::backward;
110+ case backward_transitive:
111+ return ppsr::compatibility_level::backward_transitive;
112+ case forward:
113+ return ppsr::compatibility_level::forward;
114+ case forward_transitive:
115+ return ppsr::compatibility_level::forward_transitive;
116+ case full:
117+ return ppsr::compatibility_level::full;
118+ case full_transitive:
119+ return ppsr::compatibility_level::full_transitive;
120+ case unknown:
121+ return std::nullopt ;
122+ }
123+ return std::nullopt ;
124+ }
125+
81126} // namespace
82127
83128std::optional<net::unresolved_address>
@@ -245,6 +290,78 @@ http_source_reader::read_subject_version(
245290 co_return std::move (res.value ().schema );
246291}
247292
293+ ss::future<source_result<std::optional<ppsr::mode>>>
294+ http_source_reader::read_mode (ppsr::context_subject sub, ss::abort_source& as) {
295+ auto units = co_await ss::get_units (_inflight, 1 , as);
296+ auto client = co_await ensure_client (as);
297+ if (!client.has_value ()) {
298+ co_return std::unexpected (std::move (client.error ()));
299+ }
300+ retry_chain_node rtc (as, request_timeout, request_backoff);
301+ // Prefer GET /mode over GET /mode/. for the default context to be
302+ // backwards-compatible with a source that does not implement contexts.
303+ auto res = sub.is_default_context ()
304+ ? co_await client.value ()->get_mode (rtc)
305+ : co_await client.value ()->get_subject_mode (
306+ sub, rtc, ppsr::default_to_global::no);
307+ if (!res.has_value ()) {
308+ if (std::holds_alternative<rc::subject_mode_not_found>(res.error ())) {
309+ co_return std::optional<ppsr::mode>{std::nullopt };
310+ }
311+ co_return std::unexpected (to_source_error (std::move (res.error ())));
312+ }
313+ auto narrowed = narrow_mode (res.value ().mode );
314+ if (!narrowed.has_value ()) {
315+ co_return std::unexpected (
316+ source_error{
317+ .kind = source_error_kind::operation_failed,
318+ .message = fmt::format (
319+ " source mode '{}' of {} is not supported by the destination" ,
320+ res.value ().raw ,
321+ sub)});
322+ }
323+ co_return narrowed;
324+ }
325+
326+ ss::future<source_result<std::optional<ppsr::compatibility_level>>>
327+ http_source_reader::read_config (
328+ ppsr::context_subject sub, ss::abort_source& as) {
329+ auto units = co_await ss::get_units (_inflight, 1 , as);
330+ auto client = co_await ensure_client (as);
331+ if (!client.has_value ()) {
332+ co_return std::unexpected (std::move (client.error ()));
333+ }
334+ retry_chain_node rtc (as, request_timeout, request_backoff);
335+ // Prefer GET /config over GET /config/. for the default context to be
336+ // backwards-compatible with a source that does not implement contexts.
337+ auto res = sub.is_default_context ()
338+ ? co_await client.value ()->get_config (rtc)
339+ : co_await client.value ()->get_subject_config (
340+ sub, rtc, ppsr::default_to_global::no);
341+ if (!res.has_value ()) {
342+ if (std::holds_alternative<rc::subject_config_not_found>(res.error ())) {
343+ co_return std::optional<ppsr::compatibility_level>{std::nullopt };
344+ }
345+ co_return std::unexpected (to_source_error (std::move (res.error ())));
346+ }
347+ auto narrowed = narrow_compat (res.value ().level );
348+ if (!narrowed.has_value ()) {
349+ co_return std::unexpected (
350+ source_error{
351+ .kind = source_error_kind::operation_failed,
352+ .message = fmt::format (
353+ " source compatibility level '{}' of {} is not supported by the "
354+ " destination" ,
355+ res.value ().raw ,
356+ sub)});
357+ }
358+ // Unsupported config fields (config_info::unknown_fields, e.g.
359+ // defaultRuleSet or compatibilityGroup) are ignored; honoring
360+ // unsupported_schema_feature_policy is future work, as it is for the
361+ // schema-body path in read_subject_version.
362+ co_return narrowed;
363+ }
364+
248365ss::future<> http_source_reader::stop () {
249366 // Idempotent: the reader can be stopped more than once (e.g. an in-flight
250367 // reconciler stopping the task before link teardown stops it again). The
0 commit comments