protoschema 0.1.9

📐 Programmatically define protobuf contracts using flexible, modular and reusable elements
Documentation
use std::{marker::PhantomData, sync::Arc};

use bon::Builder;

use crate::{
  field_type::{get_shortest_item_name, ImportedItemPath},
  messages::{MessageBuilder, MessageState},
  packages::Arena,
  sealed, Empty, IsUnset, ProtoOption, Set, Unset,
};

/// The builder for a protobuf service.
/// Usually generated via the [`services`](crate::services!) macro.
#[derive(Clone, Debug)]
pub struct ServiceBuilder<S: ServiceState = Empty> {
  pub(crate) id: usize,
  pub(crate) arena: Arena,
  pub(crate) file_id: usize,
  pub(crate) _phantom: PhantomData<fn() -> S>,
}

/// A struct representing a protobuf service handler.
/// Usually generated as part of the [`services`](crate::services!) macro.
#[derive(Clone, Debug, Builder)]
#[builder(start_fn = new)]
pub struct ServiceHandler {
  #[builder(start_fn)]
  pub(crate) name: Box<str>,
  #[builder(setters(vis = "", name = options_internal))]
  #[builder(default)]
  pub(crate) options: Box<[ProtoOption]>,
  #[builder(setters(vis = "", name = request_internal))]
  pub(crate) request: Arc<ImportedItemPath>,
  #[builder(setters(vis = "", name = response_internal))]
  pub(crate) response: Arc<ImportedItemPath>,
}

impl ServiceHandler {
  pub(crate) fn render_request(&self, current_file: &str, current_package: &str) -> Arc<str> {
    get_shortest_item_name(&self.request, current_file, current_package)
  }

  pub(crate) fn render_response(&self, current_file: &str, current_package: &str) -> Arc<str> {
    get_shortest_item_name(&self.response, current_file, current_package)
  }
}

use service_handler_builder::{
  IsUnset as HandlerIsUnset, SetOptions as HandlerSetOptions, SetRequest, SetResponse,
  State as HandlerState,
};

impl<S: HandlerState> ServiceHandlerBuilder<S> {
  /// Sets the options for this handler
  pub fn options<I>(self, options: I) -> ServiceHandlerBuilder<HandlerSetOptions<S>>
  where
    S::Options: HandlerIsUnset,
    I: IntoIterator<Item = ProtoOption>,
  {
    self.options_internal(options.into_iter().collect())
  }

  /// Sets the request for this handler
  pub fn request<MS: MessageState>(
    self,
    message: &MessageBuilder<MS>,
  ) -> ServiceHandlerBuilder<SetRequest<S>>
  where
    S::Request: HandlerIsUnset,
  {
    self.request_internal(message.get_import_path())
  }

  /// Sets the response for this handler
  pub fn response<MS: MessageState>(
    self,
    message: &MessageBuilder<MS>,
  ) -> ServiceHandlerBuilder<SetResponse<S>>
  where
    S::Response: HandlerIsUnset,
  {
    self.response_internal(message.get_import_path())
  }
}

#[doc(hidden)]
#[derive(Clone, Debug, Default)]
pub struct ServiceData {
  pub name: Box<str>,
  pub handlers: Box<[ServiceHandler]>,
  pub options: Box<[ProtoOption]>,
}

impl<S: ServiceState> ServiceBuilder<S> {
  /// Clones the data from the package's pool for this service and returns it
  pub fn get_data(self) -> ServiceData {
    self.arena.borrow().services[self.id].clone()
  }

  /// Gets the name of the containing file
  pub fn get_file(&self) -> Arc<str> {
    let arena = self.arena.borrow();
    arena.files[self.file_id].name.clone()
  }

  /// Gets the name of the service
  pub fn get_name(&self) -> Box<str> {
    let arena = self.arena.borrow();

    arena.services[self.id].name.clone()
  }

  /// Gets the name of the containing package
  pub fn get_package(&self) -> Arc<str> {
    self.arena.borrow().name.clone()
  }

  /// Sets the handlers for this service. Consumes the original builder and returns a new one.
  pub fn handlers<I>(self, handlers: I) -> ServiceBuilder<SetHandlers<S>>
  where
    S::Handlers: IsUnset,
    I: IntoIterator<Item = ServiceHandler>,
  {
    {
      let mut arena = self.arena.borrow_mut();
      let file = &mut arena.files[self.file_id];

      arena.services[self.id].handlers = handlers
        .into_iter()
        .inspect(|h| {
          file.conditionally_add_import(&h.request.file);
          file.conditionally_add_import(&h.response.file);
        })
        .collect();
    }

    ServiceBuilder {
      id: self.id,
      arena: self.arena,
      file_id: self.file_id,
      _phantom: PhantomData,
    }
  }

  /// Sets the options for this service. Consumes the original builder and returns a new one.
  pub fn options<I>(self, options: I) -> ServiceBuilder<SetOptions<S>>
  where
    S::Options: IsUnset,
    I: IntoIterator<Item = ProtoOption>,
  {
    {
      let mut arena = self.arena.borrow_mut();
      let service = &mut arena.services[self.id];

      service.options = options.into_iter().collect()
    }

    ServiceBuilder {
      id: self.id,
      arena: self.arena,
      file_id: self.file_id,
      _phantom: PhantomData,
    }
  }
}

#[doc(hidden)]
pub trait ServiceState: Sized {
  type Handlers;
  type Options;
  #[doc(hidden)]
  const SEALED: sealed::Sealed;
}

#[allow(non_camel_case_types)]
mod members {
  pub struct handlers;
  pub struct options;
}

#[doc(hidden)]
pub struct SetHandlers<S: ServiceState = Empty>(PhantomData<fn() -> S>);
#[doc(hidden)]
pub struct SetOptions<S: ServiceState = Empty>(PhantomData<fn() -> S>);

#[doc(hidden)]
impl ServiceState for Empty {
  type Handlers = Unset<members::handlers>;
  type Options = Unset<members::options>;
  const SEALED: sealed::Sealed = sealed::Sealed;
}

#[doc(hidden)]
impl<S: ServiceState> ServiceState for SetHandlers<S> {
  type Handlers = Set<members::handlers>;
  type Options = S::Options;
  const SEALED: sealed::Sealed = sealed::Sealed;
}

#[doc(hidden)]
impl<S: ServiceState> ServiceState for SetOptions<S> {
  type Handlers = S::Handlers;
  type Options = Set<members::options>;
  const SEALED: sealed::Sealed = sealed::Sealed;
}