Skip to main content

nisshi_storage/
service.rs

1// Copyright ⓒ 2024-2026 Peter Morgan <peter.james.morgan@gmail.com>
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15mod alter_user_scram_credentials;
16mod consumer_group_describe;
17mod create_acls;
18mod create_topics;
19mod delete_groups;
20mod delete_records;
21mod delete_topics;
22mod describe_acls;
23mod describe_cluster;
24mod describe_configs;
25mod describe_groups;
26mod describe_topic_partitions;
27mod describe_user_scram_credentials;
28mod fetch;
29mod find_coordinator;
30mod get_telemetry_subscriptions;
31mod incremental_alter_configs;
32mod init_producer_id;
33mod list_groups;
34mod list_offsets;
35mod list_partition_reassignments;
36mod metadata;
37mod produce;
38mod txn;
39
40use std::{
41    collections::BTreeMap,
42    fmt::{self, Debug, Display, Formatter},
43    sync::LazyLock,
44    time::{Duration, SystemTime},
45};
46
47pub use alter_user_scram_credentials::AlterUserScramCredentialsService;
48use async_trait::async_trait;
49pub use consumer_group_describe::ConsumerGroupDescribeService;
50pub use create_acls::CreateAclsService;
51pub use create_topics::CreateTopicsService;
52pub use delete_groups::DeleteGroupsService;
53pub use delete_records::DeleteRecordsService;
54pub use delete_topics::DeleteTopicsService;
55pub use describe_acls::DescribeAclsService;
56pub use describe_cluster::DescribeClusterService;
57pub use describe_configs::DescribeConfigsService;
58pub use describe_groups::DescribeGroupsService;
59pub use describe_topic_partitions::DescribeTopicPartitionsService;
60pub use describe_user_scram_credentials::DescribeUserScramCredentialsService;
61pub use fetch::FetchService;
62pub use find_coordinator::FindCoordinatorService;
63pub use get_telemetry_subscriptions::GetTelemetrySubscriptionsService;
64pub use incremental_alter_configs::IncrementalAlterConfigsService;
65pub use init_producer_id::InitProducerIdService;
66pub use list_groups::ListGroupsService;
67pub use list_offsets::ListOffsetsService;
68pub use list_partition_reassignments::ListPartitionReassignmentsService;
69pub use metadata::MetadataService;
70use nisshi_sans_io::{
71    ConfigResource, ErrorCode, IsolationLevel, ListOffset, ScramMechanism,
72    create_topics_request::CreatableTopic, delete_groups_response::DeletableGroupResult,
73    delete_records_request::DeleteRecordsTopic, delete_records_response::DeleteRecordsTopicResult,
74    describe_cluster_response::DescribeClusterBroker,
75    describe_configs_response::DescribeConfigsResult,
76    describe_topic_partitions_response::DescribeTopicPartitionsResponseTopic,
77    incremental_alter_configs_request::AlterConfigsResource,
78    incremental_alter_configs_response::AlterConfigsResourceResponse,
79    list_groups_response::ListedGroup, record::deflated,
80    txn_offset_commit_response::TxnOffsetCommitResponseTopic,
81};
82use opentelemetry::{
83    KeyValue,
84    metrics::{Counter, Gauge, Histogram},
85};
86pub use produce::ProduceService;
87use rama::{Context, Layer, Service};
88use tokio::sync::{
89    mpsc::{self, error::SendError},
90    oneshot,
91};
92use tokio_util::sync::CancellationToken;
93use tracing::{debug, error, instrument};
94pub use txn::add_offsets::AddOffsetsService as TxnAddOffsetsService;
95pub use txn::add_partitions::AddPartitionService as TxnAddPartitionService;
96pub use txn::end::EndService as TxnEndService;
97pub use txn::offset_commit::OffsetCommitService as TxnOffsetCommitService;
98use url::Url;
99use uuid::Uuid;
100
101use crate::{
102    BrokerRegistrationRequest, Error, GroupDetail, ListOffsetResponse, METER, MetadataResponse,
103    NamedGroupDetail, OffsetCommitRequest, OffsetStage, ProducerIdResponse, Result,
104    ScramCredential, Storage, TopicId, Topition, TxnAddPartitionsRequest, TxnAddPartitionsResponse,
105    TxnOffsetCommitRequest, UpdateError, Version,
106};
107
108#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
109pub enum Request {
110    RegisterBroker(BrokerRegistrationRequest),
111    IncrementalAlterResource(AlterConfigsResource),
112    CreateTopic {
113        topic: CreatableTopic,
114        validate_only: bool,
115    },
116    DeleteRecords(Vec<DeleteRecordsTopic>),
117    DeleteTopic(TopicId),
118    Brokers,
119    Produce {
120        transaction_id: Option<String>,
121        topition: Topition,
122        batch: deflated::Batch,
123    },
124    Fetch {
125        topition: Topition,
126        offset: i64,
127        min_bytes: u32,
128        max_bytes: u32,
129        isolation: IsolationLevel,
130        max_wait: Duration,
131    },
132    OffsetStage(Topition),
133    ListOffsets {
134        isolation_level: IsolationLevel,
135        offsets: Vec<(Topition, ListOffset)>,
136    },
137    OffsetCommit {
138        group_id: String,
139        retention_time_ms: Option<Duration>,
140        offsets: Vec<(Topition, OffsetCommitRequest)>,
141    },
142    CommittedOffsetTopitions(String),
143    OffsetFetch {
144        group_id: Option<String>,
145        topics: Vec<Topition>,
146        require_stable: Option<bool>,
147    },
148    Metadata(Option<Vec<TopicId>>),
149    DescribeConfig {
150        name: String,
151        resource: ConfigResource,
152        keys: Option<Vec<String>>,
153    },
154    DescribeTopicPartitions {
155        topics: Option<Vec<TopicId>>,
156        partition_limit: i32,
157        cursor: Option<Topition>,
158    },
159    ListGroups(Option<Vec<String>>),
160    DeleteGroups(Option<Vec<String>>),
161    DescribeGroups {
162        group_ids: Option<Vec<String>>,
163        include_authorized_operations: bool,
164    },
165    UpdateGroup {
166        group_id: String,
167        detail: GroupDetail,
168        version: Option<Version>,
169    },
170    InitProducer {
171        transaction_id: Option<String>,
172        transaction_timeout_ms: i32,
173        producer_id: Option<i64>,
174        producer_epoch: Option<i16>,
175    },
176    TxnAddOffsets {
177        transaction_id: String,
178        producer_id: i64,
179        producer_epoch: i16,
180        group_id: String,
181    },
182    TxnAddPartitions(TxnAddPartitionsRequest),
183    TxnOffsetCommit(TxnOffsetCommitRequest),
184    TxnEnd {
185        transaction_id: String,
186        producer_id: i64,
187        producer_epoch: i16,
188        committed: bool,
189    },
190    Maintain(SystemTime),
191    ClusterId,
192    Node,
193    AdvertisedListener,
194    DeleteUserScramCredential {
195        user: String,
196        mechanism: ScramMechanism,
197    },
198    UpsertUserScramCredential {
199        user: String,
200        mechanism: ScramMechanism,
201        credential: ScramCredential,
202    },
203    UserScramCredential {
204        user: String,
205        mechanism: ScramMechanism,
206    },
207    Ping,
208}
209
210impl Display for Request {
211    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
212        match self {
213            Self::AdvertisedListener => f.write_str("AdvertisedListener"),
214            Self::Brokers => f.write_str("Brokers"),
215            Self::ClusterId => f.write_str("ClusterId"),
216            Self::CommittedOffsetTopitions(_) => f.write_str("CommittedOffsetTopitions"),
217            Self::CreateTopic { .. } => f.write_str("CreateTopic"),
218            Self::DeleteGroups(_) => f.write_str("DeleteGroups"),
219            Self::DeleteRecords(_) => f.write_str("DeleteRecords"),
220            Self::DeleteTopic(_) => f.write_str("DeleteTopic"),
221            Self::DescribeConfig { .. } => f.write_str("DescribeConfig"),
222            Self::DescribeGroups { .. } => f.write_str("DescribeGroups"),
223            Self::DescribeTopicPartitions { .. } => f.write_str("DescribeTopicPartitions"),
224            Self::Fetch { .. } => f.write_str("Fetch"),
225            Self::IncrementalAlterResource(_) => f.write_str("IncrementalAlterResource"),
226            Self::InitProducer { .. } => f.write_str("InitProducer"),
227            Self::ListGroups(_) => f.write_str("ListGroups"),
228            Self::ListOffsets { .. } => f.write_str("ListOffsets"),
229            Self::Maintain(_) => f.write_str("Maintain"),
230            Self::Metadata(_) => f.write_str("Metadata"),
231            Self::Node => f.write_str("Node"),
232            Self::OffsetCommit { .. } => f.write_str("OffsetCommit"),
233            Self::OffsetFetch { .. } => f.write_str("OffsetFetch"),
234            Self::OffsetStage(_) => f.write_str("OffsetStage"),
235            Self::Produce { .. } => f.write_str("Produce"),
236            Self::RegisterBroker(_) => f.write_str("RegisterBroker"),
237            Self::TxnAddOffsets { .. } => f.write_str("TxnAddOffsets"),
238            Self::TxnAddPartitions(_) => f.write_str("TxnAddPartitions"),
239            Self::TxnEnd { .. } => f.write_str("TxnEnd"),
240            Self::TxnOffsetCommit(_) => f.write_str("TxnOffsetCommit"),
241            Self::UpdateGroup { .. } => f.write_str("UpdateGroup"),
242            Self::DeleteUserScramCredential { .. } => f.write_str("DeleteUserScramCredential"),
243            Self::UpsertUserScramCredential { .. } => f.write_str("UpsertUserScramCredential"),
244            Self::UserScramCredential { .. } => f.write_str("UserScramCredential"),
245            Self::Ping => f.write_str("Ping"),
246        }
247    }
248}
249
250#[derive(Clone, Debug)]
251pub enum Response {
252    AdvertisedListener(Result<Url>),
253    Brokers(Result<Vec<DescribeClusterBroker>>),
254    ClusterId(Result<String>),
255    CommittedOffsetTopitions(Result<BTreeMap<Topition, i64>>),
256    CreateTopic(Result<Uuid>),
257    DeleteGroups(Result<Vec<DeletableGroupResult>>),
258    DeleteRecords(Result<Vec<DeleteRecordsTopicResult>>),
259    DeleteTopic(Result<ErrorCode>),
260    DeleteUserScramCredential(Result<()>),
261    DescribeConfig(Result<DescribeConfigsResult>),
262    DescribeGroups(Result<Vec<NamedGroupDetail>>),
263    DescribeTopicPartitions(Result<Vec<DescribeTopicPartitionsResponseTopic>>),
264    Fetch(Result<Vec<deflated::Batch>>),
265    IncrementalAlterResponse(Result<AlterConfigsResourceResponse>),
266    InitProducer(Result<ProducerIdResponse>),
267    ListGroups(Result<Vec<ListedGroup>>),
268    ListOffsets(Result<Vec<(Topition, ListOffsetResponse)>>),
269    Maintain(Result<()>),
270    Metadata(Result<MetadataResponse>),
271    Node(Result<i32>),
272    OffsetCommit(Result<Vec<(Topition, ErrorCode)>>),
273    OffsetFetch(Result<BTreeMap<Topition, i64>>),
274    OffsetStage(Result<OffsetStage>),
275    Ping(Result<()>),
276    Produce(Result<i64>),
277    RegisterBroker(Result<()>),
278    TxnAddOffsets(Result<ErrorCode>),
279    TxnAddPartitions(Result<TxnAddPartitionsResponse>),
280    TxnEnd(Result<ErrorCode>),
281    TxnOffsetCommit(Result<Vec<TxnOffsetCommitResponseTopic>>),
282    UpdateGroup(Result<Version, UpdateError<GroupDetail>>),
283    UpsertUserScramCredential(Result<()>),
284    UserScramCredential(Result<Option<ScramCredential>>),
285}
286
287pub type RequestSender = mpsc::Sender<(Request, oneshot::Sender<Response>)>;
288pub type RequestReceiver = mpsc::Receiver<(Request, oneshot::Sender<Response>)>;
289
290pub fn bounded_channel(buffer: usize) -> (RequestSender, RequestReceiver) {
291    mpsc::channel::<(Request, oneshot::Sender<Response>)>(buffer)
292}
293
294#[derive(Clone, Debug, thiserror::Error)]
295pub enum ServiceError {
296    Storage(Error),
297    UpdateGroupDetail(UpdateError<GroupDetail>),
298}
299
300impl Display for ServiceError {
301    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
302        write!(f, "{self:?}")
303    }
304}
305
306impl From<SendError<()>> for ServiceError {
307    fn from(_value: SendError<()>) -> Self {
308        Self::Storage(Error::UnableToSend)
309    }
310}
311
312impl From<Error> for ServiceError {
313    fn from(value: Error) -> Self {
314        Self::Storage(value)
315    }
316}
317
318impl From<UpdateError<GroupDetail>> for ServiceError {
319    fn from(value: UpdateError<GroupDetail>) -> Self {
320        Self::UpdateGroupDetail(value)
321    }
322}
323
324impl From<ServiceError> for Error {
325    fn from(value: ServiceError) -> Self {
326        if let ServiceError::Storage(error) = value {
327            error
328        } else {
329            unreachable!()
330        }
331    }
332}
333
334impl From<ServiceError> for UpdateError<GroupDetail> {
335    fn from(value: ServiceError) -> Self {
336        if let ServiceError::UpdateGroupDetail(error) = value {
337            error
338        } else {
339            unreachable!()
340        }
341    }
342}
343
344#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
345pub struct RequestLayer;
346
347impl<S> Layer<S> for RequestLayer {
348    type Service = RequestService<S>;
349
350    fn layer(&self, inner: S) -> Self::Service {
351        Self::Service { inner }
352    }
353}
354
355#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
356pub struct RequestService<S> {
357    inner: S,
358}
359
360impl<State, S> Service<State, Request> for RequestService<S>
361where
362    S: Service<State, Request>,
363    State: Send + Sync + 'static,
364{
365    type Response = S::Response;
366    type Error = S::Error;
367
368    async fn serve(
369        &self,
370        ctx: Context<State>,
371        req: Request,
372    ) -> Result<Self::Response, Self::Error> {
373        debug!(?req);
374        self.inner.serve(ctx, req).await
375    }
376}
377
378/// A [`Service`] sending [`Request`]s over a [`RequestSender`] channel
379#[derive(Clone, Debug)]
380pub struct RequestChannelService {
381    tx: RequestSender,
382}
383
384impl RequestChannelService {
385    pub fn new(tx: RequestSender) -> Self {
386        Self { tx }
387    }
388
389    fn elapsed_millis(&self, start: SystemTime) -> u64 {
390        start
391            .elapsed()
392            .map_or(0, |duration| duration.as_millis() as u64)
393    }
394}
395
396static STORAGE_CHANNEL_CAPACITY: LazyLock<Gauge<u64>> = LazyLock::new(|| {
397    METER
398        .u64_gauge("nisshi_storage_channel_capacity")
399        .with_description("Storage channel capacity")
400        .build()
401});
402
403impl<State> Service<State, Request> for RequestChannelService
404where
405    State: Send + Sync + 'static,
406{
407    type Response = Response;
408    type Error = ServiceError;
409
410    #[instrument(skip_all)]
411    async fn serve(
412        &self,
413        ctx: Context<State>,
414        req: Request,
415    ) -> Result<Self::Response, Self::Error> {
416        let _ = ctx;
417        let (resp_tx, resp_rx) = oneshot::channel();
418
419        let start = SystemTime::now();
420
421        let operation = req.to_string();
422        let attributes = [KeyValue::new("operation", operation.clone())];
423
424        let capacity = self.tx.capacity();
425        STORAGE_CHANNEL_CAPACITY.record(capacity as u64, &attributes);
426        debug!(operation, capacity);
427
428        self.tx
429            .reserve()
430            .await
431            .map(|permit| permit.send((req, resp_tx)))
432            .inspect(|_| {
433                let permit_elapsed = self.elapsed_millis(start);
434                STORAGE_CHANNEL_PERMIT_DURATION.record(permit_elapsed, &attributes);
435                debug!(operation, permit_elapsed);
436            })
437            .inspect_err(|err| {
438                error!(operation, ?err);
439                STORAGE_CHANNEL_ERROR.add(1, &attributes);
440            })?;
441
442        resp_rx
443            .await
444            .map_err(|_| Error::OneshotRecv.into())
445            .inspect(|_| {
446                let elapsed_millis = self.elapsed_millis(start);
447                STORAGE_CHANNEL_REQUEST_DURATION.record(elapsed_millis, &attributes);
448                debug!(operation, elapsed_millis);
449            })
450            .inspect_err(|err| {
451                error!(operation, ?err);
452                STORAGE_CHANNEL_ERROR.add(1, &attributes);
453            })
454    }
455}
456
457static STORAGE_CHANNEL_REQUEST_DURATION: LazyLock<Histogram<u64>> = LazyLock::new(|| {
458    METER
459        .u64_histogram("nisshi_storage_channel_request_duration")
460        .with_unit("ms")
461        .with_description("Storage channel request latency in milliseconds")
462        .build()
463});
464
465static STORAGE_CHANNEL_PERMIT_DURATION: LazyLock<Histogram<u64>> = LazyLock::new(|| {
466    METER
467        .u64_histogram("nisshi_storage_channel_permit_duration")
468        .with_unit("ms")
469        .with_description("Storage channel permit latency in milliseconds")
470        .build()
471});
472
473static STORAGE_CHANNEL_ERROR: LazyLock<Counter<u64>> = LazyLock::new(|| {
474    METER
475        .u64_counter("nisshi_storage_channel_error")
476        .with_description("Storage channel error count")
477        .build()
478});
479
480#[async_trait]
481impl Storage for RequestChannelService {
482    #[instrument(skip_all)]
483    async fn register_broker(&self, broker_registration: BrokerRegistrationRequest) -> Result<()> {
484        self.serve(
485            Context::default(),
486            Request::RegisterBroker(broker_registration),
487        )
488        .await
489        .and_then(|response| {
490            if let Response::RegisterBroker(inner) = response {
491                inner.map_err(Into::into)
492            } else {
493                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
494            }
495        })
496        .map_err(Into::into)
497    }
498
499    #[instrument(skip_all)]
500    async fn incremental_alter_resource(
501        &self,
502        resource: AlterConfigsResource,
503    ) -> Result<AlterConfigsResourceResponse> {
504        self.serve(
505            Context::default(),
506            Request::IncrementalAlterResource(resource),
507        )
508        .await
509        .and_then(|response| {
510            if let Response::IncrementalAlterResponse(inner) = response {
511                inner.map_err(Into::into)
512            } else {
513                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
514            }
515        })
516        .map_err(Into::into)
517    }
518
519    #[instrument(skip_all)]
520    async fn create_topic(&self, topic: CreatableTopic, validate_only: bool) -> Result<Uuid> {
521        self.serve(
522            Context::default(),
523            Request::CreateTopic {
524                topic,
525                validate_only,
526            },
527        )
528        .await
529        .and_then(|response| {
530            if let Response::CreateTopic(inner) = response {
531                inner.map_err(Into::into)
532            } else {
533                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
534            }
535        })
536        .map_err(Into::into)
537    }
538
539    #[instrument(skip_all)]
540    async fn delete_records(
541        &self,
542        topics: &[DeleteRecordsTopic],
543    ) -> Result<Vec<DeleteRecordsTopicResult>> {
544        self.serve(
545            Context::default(),
546            Request::DeleteRecords(Vec::from(topics)),
547        )
548        .await
549        .and_then(|response| {
550            if let Response::DeleteRecords(inner) = response {
551                inner.map_err(Into::into)
552            } else {
553                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
554            }
555        })
556        .map_err(Into::into)
557    }
558
559    #[instrument(skip_all)]
560    async fn delete_topic(&self, topic: &TopicId) -> Result<ErrorCode> {
561        self.serve(Context::default(), Request::DeleteTopic(topic.to_owned()))
562            .await
563            .and_then(|response| {
564                if let Response::DeleteTopic(inner) = response {
565                    inner.map_err(Into::into)
566                } else {
567                    Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
568                }
569            })
570            .map_err(Into::into)
571    }
572
573    #[instrument(skip_all)]
574    async fn brokers(&self) -> Result<Vec<DescribeClusterBroker>> {
575        self.serve(Context::default(), Request::Brokers)
576            .await
577            .and_then(|response| {
578                if let Response::Brokers(inner) = response {
579                    inner.map_err(Into::into)
580                } else {
581                    Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
582                }
583            })
584            .map_err(Into::into)
585    }
586
587    #[instrument(skip_all)]
588    async fn produce(
589        &self,
590        transaction_id: Option<&str>,
591        topition: &Topition,
592        batch: deflated::Batch,
593    ) -> Result<i64> {
594        let transaction_id = transaction_id.map(|s| s.to_string());
595        let topition = topition.to_owned();
596
597        self.serve(
598            Context::default(),
599            Request::Produce {
600                transaction_id,
601                topition,
602                batch,
603            },
604        )
605        .await
606        .and_then(|response| {
607            if let Response::Produce(inner) = response {
608                inner.map_err(Into::into)
609            } else {
610                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
611            }
612        })
613        .map_err(Into::into)
614    }
615
616    #[instrument(skip_all)]
617    async fn fetch(
618        &self,
619        topition: &'_ Topition,
620        offset: i64,
621        min_bytes: u32,
622        max_bytes: u32,
623        isolation: IsolationLevel,
624        max_wait: Duration,
625    ) -> Result<Vec<deflated::Batch>> {
626        let topition = topition.to_owned();
627
628        self.serve(
629            Context::default(),
630            Request::Fetch {
631                topition,
632                offset,
633                min_bytes,
634                max_bytes,
635                isolation,
636                max_wait,
637            },
638        )
639        .await
640        .and_then(|response| {
641            if let Response::Fetch(inner) = response {
642                inner.map_err(Into::into)
643            } else {
644                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
645            }
646        })
647        .map_err(Into::into)
648    }
649
650    #[instrument(skip_all)]
651    async fn offset_stage(&self, topition: &Topition) -> Result<OffsetStage> {
652        self.serve(
653            Context::default(),
654            Request::OffsetStage(topition.to_owned()),
655        )
656        .await
657        .and_then(|response| {
658            if let Response::OffsetStage(inner) = response {
659                inner.map_err(Into::into)
660            } else {
661                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
662            }
663        })
664        .map_err(Into::into)
665    }
666
667    #[instrument(skip_all)]
668    async fn list_offsets(
669        &self,
670        isolation_level: IsolationLevel,
671        offsets: &[(Topition, ListOffset)],
672    ) -> Result<Vec<(Topition, ListOffsetResponse)>> {
673        let offsets = Vec::from(offsets);
674
675        self.serve(
676            Context::default(),
677            Request::ListOffsets {
678                isolation_level,
679                offsets,
680            },
681        )
682        .await
683        .and_then(|response| {
684            if let Response::ListOffsets(inner) = response {
685                inner.map_err(Into::into)
686            } else {
687                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
688            }
689        })
690        .map_err(Into::into)
691    }
692
693    #[instrument(skip_all)]
694    async fn offset_commit(
695        &self,
696        group_id: &str,
697        retention_time_ms: Option<Duration>,
698        offsets: &[(Topition, OffsetCommitRequest)],
699    ) -> Result<Vec<(Topition, ErrorCode)>> {
700        let group_id = group_id.to_string();
701        let offsets = Vec::from(offsets);
702
703        self.serve(
704            Context::default(),
705            Request::OffsetCommit {
706                group_id,
707                retention_time_ms,
708                offsets,
709            },
710        )
711        .await
712        .and_then(|response| {
713            if let Response::OffsetCommit(inner) = response {
714                inner.map_err(Into::into)
715            } else {
716                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
717            }
718        })
719        .map_err(Into::into)
720    }
721
722    #[instrument(skip_all)]
723    async fn committed_offset_topitions(&self, group_id: &str) -> Result<BTreeMap<Topition, i64>> {
724        let group_id = group_id.to_string();
725
726        self.serve(
727            Context::default(),
728            Request::CommittedOffsetTopitions(group_id),
729        )
730        .await
731        .and_then(|response| {
732            if let Response::CommittedOffsetTopitions(inner) = response {
733                inner.map_err(Into::into)
734            } else {
735                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
736            }
737        })
738        .map_err(Into::into)
739    }
740
741    #[instrument(skip_all)]
742    async fn offset_fetch(
743        &self,
744        group_id: Option<&str>,
745        topics: &[Topition],
746        require_stable: Option<bool>,
747    ) -> Result<BTreeMap<Topition, i64>> {
748        let group_id = group_id.map(|s| s.to_string());
749        let topics = Vec::from(topics);
750
751        self.serve(
752            Context::default(),
753            Request::OffsetFetch {
754                group_id,
755                topics,
756                require_stable,
757            },
758        )
759        .await
760        .and_then(|response| {
761            if let Response::OffsetFetch(inner) = response {
762                inner.map_err(Into::into)
763            } else {
764                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
765            }
766        })
767        .map_err(Into::into)
768    }
769
770    #[instrument(skip_all)]
771    async fn metadata(&self, topics: Option<&[TopicId]>) -> Result<MetadataResponse> {
772        let topics = topics.map(Vec::from);
773
774        self.serve(Context::default(), Request::Metadata(topics))
775            .await
776            .and_then(|response| {
777                if let Response::Metadata(inner) = response {
778                    inner.map_err(Into::into)
779                } else {
780                    Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
781                }
782            })
783            .map_err(Into::into)
784    }
785
786    #[instrument(skip_all)]
787    async fn describe_config(
788        &self,
789        name: &str,
790        resource: ConfigResource,
791        keys: Option<&[String]>,
792    ) -> Result<DescribeConfigsResult> {
793        let name = name.to_string();
794        let keys = keys.map(Vec::from);
795
796        self.serve(
797            Context::default(),
798            Request::DescribeConfig {
799                name,
800                resource,
801                keys,
802            },
803        )
804        .await
805        .and_then(|response| {
806            if let Response::DescribeConfig(inner) = response {
807                inner.map_err(Into::into)
808            } else {
809                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
810            }
811        })
812        .map_err(Into::into)
813    }
814
815    #[instrument(skip_all)]
816    async fn describe_topic_partitions(
817        &self,
818        topics: Option<&[TopicId]>,
819        partition_limit: i32,
820        cursor: Option<Topition>,
821    ) -> Result<Vec<DescribeTopicPartitionsResponseTopic>> {
822        let topics = topics.map(Vec::from);
823
824        self.serve(
825            Context::default(),
826            Request::DescribeTopicPartitions {
827                topics,
828                partition_limit,
829                cursor,
830            },
831        )
832        .await
833        .and_then(|response| {
834            if let Response::DescribeTopicPartitions(inner) = response {
835                inner.map_err(Into::into)
836            } else {
837                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
838            }
839        })
840        .map_err(Into::into)
841    }
842
843    #[instrument(skip_all)]
844    async fn list_groups(&self, states_filter: Option<&[String]>) -> Result<Vec<ListedGroup>> {
845        let states_filter = states_filter.map(Vec::from);
846
847        self.serve(Context::default(), Request::ListGroups(states_filter))
848            .await
849            .and_then(|response| {
850                if let Response::ListGroups(inner) = response {
851                    inner.map_err(Into::into)
852                } else {
853                    Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
854                }
855            })
856            .map_err(Into::into)
857    }
858
859    #[instrument(skip_all)]
860    async fn delete_groups(
861        &self,
862        group_ids: Option<&[String]>,
863    ) -> Result<Vec<DeletableGroupResult>> {
864        let group_ids = group_ids.map(Vec::from);
865
866        self.serve(Context::default(), Request::DeleteGroups(group_ids))
867            .await
868            .and_then(|response| {
869                if let Response::DeleteGroups(inner) = response {
870                    inner.map_err(Into::into)
871                } else {
872                    Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
873                }
874            })
875            .map_err(Into::into)
876    }
877
878    #[instrument(skip_all)]
879    async fn describe_groups(
880        &self,
881        group_ids: Option<&[String]>,
882        include_authorized_operations: bool,
883    ) -> Result<Vec<NamedGroupDetail>> {
884        let group_ids = group_ids.map(Vec::from);
885
886        self.serve(
887            Context::default(),
888            Request::DescribeGroups {
889                group_ids,
890                include_authorized_operations,
891            },
892        )
893        .await
894        .and_then(|response| {
895            if let Response::DescribeGroups(inner) = response {
896                inner.map_err(Into::into)
897            } else {
898                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
899            }
900        })
901        .map_err(Into::into)
902    }
903
904    #[instrument(skip_all)]
905    async fn update_group(
906        &self,
907        group_id: &str,
908        detail: GroupDetail,
909        version: Option<Version>,
910    ) -> Result<Version, UpdateError<GroupDetail>> {
911        let group_id = group_id.to_string();
912
913        self.serve(
914            Context::default(),
915            Request::UpdateGroup {
916                group_id,
917                detail,
918                version,
919            },
920        )
921        .await
922        .and_then(|response| {
923            if let Response::UpdateGroup(inner) = response {
924                inner.map_err(Into::into)
925            } else {
926                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
927            }
928        })
929        .map_err(Into::into)
930    }
931
932    #[instrument(skip_all)]
933    async fn init_producer(
934        &self,
935        transaction_id: Option<&str>,
936        transaction_timeout_ms: i32,
937        producer_id: Option<i64>,
938        producer_epoch: Option<i16>,
939    ) -> Result<ProducerIdResponse> {
940        let transaction_id = transaction_id.map(|transaction_id| transaction_id.to_owned());
941
942        self.serve(
943            Context::default(),
944            Request::InitProducer {
945                transaction_id,
946                transaction_timeout_ms,
947                producer_id,
948                producer_epoch,
949            },
950        )
951        .await
952        .and_then(|response| {
953            if let Response::InitProducer(inner) = response {
954                inner.map_err(Into::into)
955            } else {
956                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
957            }
958        })
959        .map_err(Into::into)
960    }
961
962    #[instrument(skip_all)]
963    async fn txn_add_offsets(
964        &self,
965        transaction_id: &str,
966        producer_id: i64,
967        producer_epoch: i16,
968        group_id: &str,
969    ) -> Result<ErrorCode> {
970        let transaction_id = transaction_id.to_string();
971        let group_id = group_id.to_string();
972
973        self.serve(
974            Context::default(),
975            Request::TxnAddOffsets {
976                transaction_id,
977                producer_id,
978                producer_epoch,
979                group_id,
980            },
981        )
982        .await
983        .and_then(|response| {
984            if let Response::TxnAddOffsets(inner) = response {
985                inner.map_err(Into::into)
986            } else {
987                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
988            }
989        })
990        .map_err(Into::into)
991    }
992
993    #[instrument(skip_all)]
994    async fn txn_add_partitions(
995        &self,
996        partitions: TxnAddPartitionsRequest,
997    ) -> Result<TxnAddPartitionsResponse> {
998        self.serve(Context::default(), Request::TxnAddPartitions(partitions))
999            .await
1000            .and_then(|response| {
1001                if let Response::TxnAddPartitions(inner) = response {
1002                    inner.map_err(Into::into)
1003                } else {
1004                    Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
1005                }
1006            })
1007            .map_err(Into::into)
1008    }
1009
1010    #[instrument(skip_all)]
1011    async fn txn_offset_commit(
1012        &self,
1013        offsets: TxnOffsetCommitRequest,
1014    ) -> Result<Vec<TxnOffsetCommitResponseTopic>> {
1015        self.serve(Context::default(), Request::TxnOffsetCommit(offsets))
1016            .await
1017            .and_then(|response| {
1018                if let Response::TxnOffsetCommit(inner) = response {
1019                    inner.map_err(Into::into)
1020                } else {
1021                    Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
1022                }
1023            })
1024            .map_err(Into::into)
1025    }
1026
1027    #[instrument(skip_all)]
1028    async fn txn_end(
1029        &self,
1030        transaction_id: &str,
1031        producer_id: i64,
1032        producer_epoch: i16,
1033        committed: bool,
1034    ) -> Result<ErrorCode> {
1035        let transaction_id = transaction_id.to_string();
1036
1037        self.serve(
1038            Context::default(),
1039            Request::TxnEnd {
1040                transaction_id,
1041                producer_id,
1042                producer_epoch,
1043                committed,
1044            },
1045        )
1046        .await
1047        .and_then(|response| {
1048            if let Response::TxnEnd(inner) = response {
1049                inner.map_err(Into::into)
1050            } else {
1051                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
1052            }
1053        })
1054        .map_err(Into::into)
1055    }
1056
1057    #[instrument(skip_all)]
1058    async fn maintain(&self, now: SystemTime) -> Result<()> {
1059        self.serve(Context::default(), Request::Maintain(now))
1060            .await
1061            .and_then(|response| {
1062                if let Response::Maintain(inner) = response {
1063                    inner.map_err(Into::into)
1064                } else {
1065                    Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
1066                }
1067            })
1068            .map_err(Into::into)
1069    }
1070
1071    #[instrument(skip_all)]
1072    async fn cluster_id(&self) -> Result<String> {
1073        self.serve(Context::default(), Request::ClusterId)
1074            .await
1075            .and_then(|response| {
1076                if let Response::ClusterId(inner) = response {
1077                    inner.map_err(Into::into)
1078                } else {
1079                    Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
1080                }
1081            })
1082            .map_err(Into::into)
1083    }
1084
1085    #[instrument(skip_all)]
1086    async fn node(&self) -> Result<i32> {
1087        self.serve(Context::default(), Request::Node)
1088            .await
1089            .and_then(|response| {
1090                if let Response::Node(inner) = response {
1091                    inner.map_err(Into::into)
1092                } else {
1093                    Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
1094                }
1095            })
1096            .map_err(Into::into)
1097    }
1098
1099    #[instrument(skip_all)]
1100    async fn advertised_listener(&self) -> Result<Url> {
1101        self.serve(Context::default(), Request::AdvertisedListener)
1102            .await
1103            .and_then(|response| {
1104                if let Response::AdvertisedListener(inner) = response {
1105                    inner.map_err(Into::into)
1106                } else {
1107                    Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
1108                }
1109            })
1110            .map_err(Into::into)
1111    }
1112
1113    #[instrument(skip_all)]
1114    async fn delete_user_scram_credential(
1115        &self,
1116        user: &str,
1117        mechanism: ScramMechanism,
1118    ) -> Result<()> {
1119        let user = user.to_string();
1120
1121        self.serve(
1122            Context::default(),
1123            Request::DeleteUserScramCredential { user, mechanism },
1124        )
1125        .await
1126        .and_then(|response| {
1127            if let Response::DeleteUserScramCredential(inner) = response {
1128                inner.map_err(Into::into)
1129            } else {
1130                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
1131            }
1132        })
1133        .map_err(Into::into)
1134    }
1135
1136    #[instrument(skip_all)]
1137    async fn upsert_user_scram_credential(
1138        &self,
1139        user: &str,
1140        mechanism: ScramMechanism,
1141        credential: ScramCredential,
1142    ) -> Result<()> {
1143        let user = user.to_string();
1144
1145        self.serve(
1146            Context::default(),
1147            Request::UpsertUserScramCredential {
1148                user,
1149                mechanism,
1150                credential,
1151            },
1152        )
1153        .await
1154        .and_then(|response| {
1155            if let Response::UpsertUserScramCredential(inner) = response {
1156                inner.map_err(Into::into)
1157            } else {
1158                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
1159            }
1160        })
1161        .map_err(Into::into)
1162    }
1163
1164    #[instrument(skip_all)]
1165    async fn user_scram_credential(
1166        &self,
1167        user: &str,
1168        mechanism: ScramMechanism,
1169    ) -> Result<Option<ScramCredential>> {
1170        let user = user.to_string();
1171
1172        self.serve(
1173            Context::default(),
1174            Request::UserScramCredential { user, mechanism },
1175        )
1176        .await
1177        .and_then(|response| {
1178            if let Response::UserScramCredential(inner) = response {
1179                inner.map_err(Into::into)
1180            } else {
1181                Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
1182            }
1183        })
1184        .map_err(Into::into)
1185    }
1186
1187    #[instrument(skip_all)]
1188    async fn ping(&self) -> Result<()> {
1189        self.serve(Context::default(), Request::Ping)
1190            .await
1191            .and_then(|response| {
1192                if let Response::Ping(inner) = response {
1193                    inner.map_err(Into::into)
1194                } else {
1195                    Err(Error::UnexpectedServiceResponse(Box::new(response)).into())
1196                }
1197            })
1198            .map_err(Into::into)
1199    }
1200}
1201
1202#[derive(Clone, Debug, Default)]
1203pub struct ChannelRequestLayer {
1204    cancellation: CancellationToken,
1205}
1206
1207impl ChannelRequestLayer {
1208    pub fn new(cancellation: CancellationToken) -> Self {
1209        Self { cancellation }
1210    }
1211}
1212
1213impl<S> Layer<S> for ChannelRequestLayer {
1214    type Service = ChannelRequestService<S>;
1215
1216    fn layer(&self, inner: S) -> Self::Service {
1217        Self::Service {
1218            inner,
1219            cancellation: self.cancellation.clone(),
1220        }
1221    }
1222}
1223
1224#[derive(Clone, Debug, Default)]
1225pub struct ChannelRequestService<S> {
1226    inner: S,
1227    cancellation: CancellationToken,
1228}
1229
1230impl<S, State> Service<State, RequestReceiver> for ChannelRequestService<S>
1231where
1232    S: Service<State, Request, Response = Response, Error = Error>,
1233    State: Clone + Send + Sync + 'static,
1234{
1235    type Response = ();
1236    type Error = Error;
1237
1238    async fn serve(
1239        &self,
1240        ctx: Context<State>,
1241        mut req: RequestReceiver,
1242    ) -> Result<Self::Response, Self::Error> {
1243        loop {
1244            tokio::select! {
1245                Some((request, tx)) = req.recv() => {
1246                    self.inner
1247                    .serve(ctx.clone(), request)
1248                    .await
1249                    .and_then(|response| {
1250                        tx.send(response).map_err(|_unsent| Error::UnableToSend)
1251                    })?
1252                }
1253
1254                cancelled = self.cancellation.cancelled() => {
1255                    debug!(?cancelled);
1256                    break;
1257                }
1258            }
1259        }
1260
1261        Ok(())
1262    }
1263}
1264
1265#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
1266pub struct RequestStorageService<G> {
1267    storage: G,
1268}
1269
1270impl<G> RequestStorageService<G>
1271where
1272    G: Storage,
1273{
1274    pub fn new(storage: G) -> Self {
1275        Self { storage }
1276    }
1277}
1278
1279impl<G, State> Service<State, Request> for RequestStorageService<G>
1280where
1281    G: Storage,
1282    State: Clone + Send + Sync + 'static,
1283{
1284    type Response = Response;
1285    type Error = Error;
1286
1287    async fn serve(
1288        &self,
1289        _ctx: Context<State>,
1290        req: Request,
1291    ) -> Result<Self::Response, Self::Error> {
1292        match req {
1293            Request::RegisterBroker(broker_registration) => Ok(Response::RegisterBroker(
1294                self.storage.register_broker(broker_registration).await,
1295            )),
1296            Request::IncrementalAlterResource(alter_configs_resource) => {
1297                Ok(Response::IncrementalAlterResponse(
1298                    self.storage
1299                        .incremental_alter_resource(alter_configs_resource)
1300                        .await,
1301                ))
1302            }
1303            Request::CreateTopic {
1304                topic,
1305                validate_only,
1306            } => Ok(Response::CreateTopic(
1307                self.storage.create_topic(topic, validate_only).await,
1308            )),
1309            Request::DeleteRecords(delete_records_topics) => Ok(Response::DeleteRecords(
1310                self.storage
1311                    .delete_records(&delete_records_topics[..])
1312                    .await,
1313            )),
1314            Request::DeleteTopic(topic_id) => Ok(Response::DeleteTopic(
1315                self.storage.delete_topic(&topic_id).await,
1316            )),
1317            Request::Brokers => Ok(Response::Brokers(self.storage.brokers().await)),
1318            Request::Produce {
1319                transaction_id,
1320                topition,
1321                batch,
1322            } => Ok(Response::Produce(
1323                self.storage
1324                    .produce(transaction_id.as_deref(), &topition, batch)
1325                    .await,
1326            )),
1327            Request::Fetch {
1328                topition,
1329                offset,
1330                min_bytes,
1331                max_bytes,
1332                isolation,
1333                max_wait,
1334            } => Ok(Response::Fetch(
1335                self.storage
1336                    .fetch(&topition, offset, min_bytes, max_bytes, isolation, max_wait)
1337                    .await,
1338            )),
1339            Request::OffsetStage(topition) => Ok(Response::OffsetStage(
1340                self.storage.offset_stage(&topition).await,
1341            )),
1342            Request::ListOffsets {
1343                isolation_level,
1344                offsets,
1345            } => Ok(Response::ListOffsets(
1346                self.storage
1347                    .list_offsets(isolation_level, &offsets[..])
1348                    .await,
1349            )),
1350            Request::OffsetCommit {
1351                group_id,
1352                retention_time_ms,
1353                offsets,
1354            } => Ok(Response::OffsetCommit(
1355                self.storage
1356                    .offset_commit(&group_id, retention_time_ms, &offsets[..])
1357                    .await,
1358            )),
1359            Request::CommittedOffsetTopitions(group_id) => Ok(Response::CommittedOffsetTopitions(
1360                self.storage.committed_offset_topitions(&group_id).await,
1361            )),
1362            Request::OffsetFetch {
1363                group_id,
1364                topics,
1365                require_stable,
1366            } => Ok(Response::OffsetFetch(
1367                self.storage
1368                    .offset_fetch(group_id.as_deref(), &topics[..], require_stable)
1369                    .await,
1370            )),
1371            Request::Metadata(topic_ids) => Ok(Response::Metadata(
1372                self.storage.metadata(topic_ids.as_deref()).await,
1373            )),
1374            Request::DescribeConfig {
1375                name,
1376                resource,
1377                keys,
1378            } => Ok(Response::DescribeConfig(
1379                self.storage
1380                    .describe_config(&name, resource, keys.as_deref())
1381                    .await,
1382            )),
1383            Request::DescribeTopicPartitions {
1384                topics,
1385                partition_limit,
1386                cursor,
1387            } => Ok(Response::DescribeTopicPartitions(
1388                self.storage
1389                    .describe_topic_partitions(topics.as_deref(), partition_limit, cursor)
1390                    .await,
1391            )),
1392            Request::ListGroups(items) => Ok(Response::ListGroups(
1393                self.storage.list_groups(items.as_deref()).await,
1394            )),
1395            Request::DeleteGroups(items) => Ok(Response::DeleteGroups(
1396                self.storage.delete_groups(items.as_deref()).await,
1397            )),
1398            Request::DescribeGroups {
1399                group_ids,
1400                include_authorized_operations,
1401            } => Ok(Response::DescribeGroups(
1402                self.storage
1403                    .describe_groups(group_ids.as_deref(), include_authorized_operations)
1404                    .await,
1405            )),
1406            Request::UpdateGroup {
1407                group_id,
1408                detail,
1409                version,
1410            } => Ok(Response::UpdateGroup(
1411                self.storage.update_group(&group_id, detail, version).await,
1412            )),
1413            Request::InitProducer {
1414                transaction_id,
1415                transaction_timeout_ms,
1416                producer_id,
1417                producer_epoch,
1418            } => Ok(Response::InitProducer(
1419                self.storage
1420                    .init_producer(
1421                        transaction_id.as_deref(),
1422                        transaction_timeout_ms,
1423                        producer_id,
1424                        producer_epoch,
1425                    )
1426                    .await,
1427            )),
1428            Request::TxnAddOffsets {
1429                transaction_id,
1430                producer_id,
1431                producer_epoch,
1432                group_id,
1433            } => Ok(Response::TxnAddOffsets(
1434                self.storage
1435                    .txn_add_offsets(&transaction_id, producer_id, producer_epoch, &group_id)
1436                    .await,
1437            )),
1438            Request::TxnAddPartitions(txn_add_partitions_request) => {
1439                Ok(Response::TxnAddPartitions(
1440                    self.storage
1441                        .txn_add_partitions(txn_add_partitions_request)
1442                        .await,
1443                ))
1444            }
1445            Request::TxnOffsetCommit(txn_offset_commit_request) => Ok(Response::TxnOffsetCommit(
1446                self.storage
1447                    .txn_offset_commit(txn_offset_commit_request)
1448                    .await,
1449            )),
1450            Request::TxnEnd {
1451                transaction_id,
1452                producer_id,
1453                producer_epoch,
1454                committed,
1455            } => Ok(Response::TxnEnd(
1456                self.storage
1457                    .txn_end(&transaction_id, producer_id, producer_epoch, committed)
1458                    .await,
1459            )),
1460            Request::Maintain(now) => Ok(Response::Maintain(self.storage.maintain(now).await)),
1461            Request::ClusterId => Ok(Response::ClusterId(self.storage.cluster_id().await)),
1462            Request::Node => Ok(Response::Node(self.storage.node().await)),
1463            Request::AdvertisedListener => Ok(Response::AdvertisedListener(
1464                self.storage.advertised_listener().await,
1465            )),
1466            Request::DeleteUserScramCredential { user, mechanism } => {
1467                Ok(Response::DeleteUserScramCredential(
1468                    self.storage
1469                        .delete_user_scram_credential(&user[..], mechanism)
1470                        .await,
1471                ))
1472            }
1473            Request::UpsertUserScramCredential {
1474                user,
1475                mechanism,
1476                credential,
1477            } => Ok(Response::UpsertUserScramCredential(
1478                self.storage
1479                    .upsert_user_scram_credential(&user[..], mechanism, credential)
1480                    .await,
1481            )),
1482            Request::UserScramCredential { user, mechanism } => Ok(Response::UserScramCredential(
1483                self.storage
1484                    .user_scram_credential(&user[..], mechanism)
1485                    .await,
1486            )),
1487            Request::Ping => Ok(Response::Ping(self.storage.ping().await)),
1488        }
1489    }
1490}