. #23 - add documentation

This commit is contained in:
Jeremy Chone
2024-09-13 16:21:54 -07:00
parent 2b776ec509
commit 158efa18ec
30 changed files with 213 additions and 45 deletions

View File

@@ -12,6 +12,7 @@ repository = "https://github.com/jeremychone/rust-genai"
[lints.rust]
unsafe_code = "forbid"
# unused = { level = "allow", priority = -1 } # For exploratory dev.
missing_docs = "warn"
[dependencies]
# -- Async

View File

@@ -1,3 +1,5 @@
//! Base examples showing the core capability of genai
use genai::chat::printer::{print_chat_stream, PrintChatStreamOptions};
use genai::chat::{ChatMessage, ChatRequest};
use genai::Client;

View File

@@ -1,3 +1,5 @@
//! Example showing how to create a conversation with genai.
use genai::chat::printer::print_chat_stream;
use genai::chat::{ChatMessage, ChatRequest};
use genai::Client;

View File

@@ -1,3 +1,6 @@
//! This example demonstrates how to use a custom authentication function to override the default AuthData resolution
//! for any specific adapter (which is based on environment variables).
use genai::chat::printer::print_chat_stream;
use genai::chat::{ChatMessage, ChatRequest};
use genai::resolver::{AuthData, AuthResolver};
@@ -5,9 +8,6 @@ use genai::{Client, ModelIden};
const MODEL: &str = "gpt-4o-mini";
/// This example demonstrates how to use a custom authentication function to override the default AuthData resolution
/// for any specific adapter (which is based on environment variables).
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let questions = &[

View File

@@ -1,3 +1,6 @@
//! This example demonstrates how to use the ModelMapper to map a ModelIden (model identifier) to
//! a potentially different one, using the model mapper.
use genai::adapter::AdapterKind;
use genai::chat::printer::print_chat_stream;
use genai::chat::{ChatMessage, ChatRequest};
@@ -7,9 +10,6 @@ use genai::{Client, ModelIden};
// NOTE: This will be overriden below to `gpt-4o-mini`
const MODEL: &str = "gpt-4o";
/// This example demonstrates how to use the ModelMapper to map a ModelIden (model identifier) to
/// a potentially different one, using the model mapper.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let questions = &[

View File

@@ -1,3 +1,7 @@
//! This example shows how to use a custom AdapterKindResolver to have some custom
//! mapping from a model name to a AdapterKind.
//! This allows to map missing models to their Adapter implementations.
use genai::chat::printer::print_chat_stream;
use genai::chat::{ChatMessage, ChatOptions, ChatRequest};
use genai::{Client, ClientConfig};
@@ -9,10 +13,6 @@ use genai::{Client, ClientConfig};
// const MODEL: &str = "llama3-8b-8192";
const MODEL: &str = "gemma:2b";
/// This example shows how to use a custom AdapterKindResolver to have some custom
/// mapping from a model name to a AdapterKind.
/// This allows to map missing models to their Adapter implementations.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let question = "Why is the sky red?";

View File

@@ -3,13 +3,20 @@ use crate::Result;
use derive_more::Display;
use serde::{Deserialize, Serialize};
/// AdapterKind is an enum that represents the different kinds of adapters that can be used to interact with the API.
#[derive(Debug, Clone, Copy, Display, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum AdapterKind {
/// Main Adapter kind for the OpenAI service.
OpenAI,
/// Used for the Ollama adapter (right now, localhost only). Behind the scenes, it uses the OpenAI Adapter logic.
Ollama,
/// Used for the Anthropic adapter.
Anthropic,
/// Used for the Cohere adapter.
Cohere,
/// Used for the Gemini adapter.
Gemini,
/// Used for the Groq adapter. Behind the scenes, it uses the OpenAI Adapter logic with the necessary Groq differences (e.g., usage).
Groq,
// Note: Variants will probably be suffixed
// AnthropicBedrock,
@@ -17,6 +24,7 @@ pub enum AdapterKind {
/// Serialization impls
impl AdapterKind {
/// Serialize to a static str
pub fn as_str(&self) -> &'static str {
match self {
AdapterKind::OpenAI => "OpenAI",
@@ -28,6 +36,7 @@ impl AdapterKind {
}
}
/// Serialize to a static str
pub fn as_lower_str(&self) -> &'static str {
match self {
AdapterKind::OpenAI => "openai",

View File

@@ -8,6 +8,10 @@
use crate::chat::chat_response_format::ChatResponseFormat;
use serde::{Deserialize, Serialize};
/// Chat Options that is taken in account for any `Client::exec...` calls.
///
/// A fallback `ChatOptions` can also be set at the `Client` at the client builder phase
/// ``
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChatOptions {
/// Will be set for this request if Adapter/providers supports it.
@@ -83,6 +87,7 @@ impl ChatOptions {
self
}
/// Set the `response_format` for this request.
pub fn with_response_format(mut self, res_format: impl Into<ChatResponseFormat>) -> Self {
self.response_format = Some(res_format.into());
self

View File

@@ -1,18 +1,23 @@
//! This module contains all the types related to a Chat Request (except ChatOptions, which has its own file).
use crate::chat::MessageContent;
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
// region: --- ChatRequest
/// The Chat request when doing a direct `Client::`
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChatRequest {
/// The initial system of the request.
pub system: Option<String>,
/// The messages of the request.
pub messages: Vec<ChatMessage>,
}
/// Constructors
impl ChatRequest {
/// Create a new ChatRequest with the given messages.
pub fn new(messages: Vec<ChatMessage>) -> Self {
Self { messages, system: None }
}
@@ -28,11 +33,13 @@ impl ChatRequest {
/// Chainable Setters
impl ChatRequest {
/// Set the system content of the request.
pub fn with_system(mut self, system: impl Into<String>) -> Self {
self.system = Some(system.into());
self
}
/// Append a message to the request.
pub fn append_message(mut self, msg: ChatMessage) -> Self {
self.messages.push(msg);
self
@@ -85,15 +92,22 @@ impl ChatRequest {
// region: --- ChatMessage
/// An individual Chat message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessage {
/// The role of the message
pub role: ChatRole,
/// The content of the message
pub content: MessageContent,
/// Extra information about the message
pub extra: Option<MessageExtra>,
}
/// Constructors
impl ChatMessage {
/// Create a new ChatMessage with the role `ChatRole::System`
pub fn system(content: impl Into<MessageContent>) -> Self {
Self {
role: ChatRole::System,
@@ -102,6 +116,7 @@ impl ChatMessage {
}
}
/// Create a new ChatMessage with the role `ChatRole::Assistant`
pub fn assistant(content: impl Into<MessageContent>) -> Self {
Self {
role: ChatRole::Assistant,
@@ -110,6 +125,7 @@ impl ChatMessage {
}
}
/// Create a new ChatMessage with the role `ChatRole::Tool`
pub fn user(content: impl Into<MessageContent>) -> Self {
Self {
role: ChatRole::User,
@@ -119,7 +135,9 @@ impl ChatMessage {
}
}
/// Chat roles
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(missing_docs)]
pub enum ChatRole {
System,
User,
@@ -127,11 +145,14 @@ pub enum ChatRole {
Tool,
}
/// NOTE: DO NOT USE, just a placeholder for now
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(missing_docs)]
pub enum MessageExtra {
Tool(ToolExtra),
}
/// NOTE: DO NOT USE, just a placeholder for now
#[allow(unused)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolExtra {

View File

@@ -7,10 +7,17 @@ use crate::ModelIden;
// region: --- ChatResponse
/// The Chat response when doing a direct `Client::`
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatResponse {
/// The eventual content of the chat response
pub content: Option<MessageContent>,
/// The eventual usage of the chat response
pub usage: MetaUsage,
/// The Model Identifier (AdapterKind/ModelName) used for this request.
/// > NOTE: This might be different than the request model if changed by the ModelMapper
pub model_iden: ModelIden,
}
@@ -33,8 +40,12 @@ impl ChatResponse {
// region: --- ChatStreamResponse
/// The result returned from
pub struct ChatStreamResponse {
/// The stream result to iterate through the stream events
pub stream: ChatStream,
/// The Model Identifier (AdapterKind/ModelName) used for this request.
pub model_iden: ModelIden,
}
@@ -45,8 +56,12 @@ pub struct ChatStreamResponse {
/// IMPORTANT: This is **NOT SUPPORTED** for now. To show the API direction.
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct MetaUsage {
/// The number of input token if returned by the api call.
pub input_tokens: Option<i32>,
/// The number of output token if returned by the api call.
pub output_tokens: Option<i32>,
/// The total number of token if returned by the api call.
/// Either the total_tokens if returned, or sum of input/output if not specified in the response
pub total_tokens: Option<i32>,
}

View File

@@ -2,22 +2,39 @@ use derive_more::From;
use serde::{Deserialize, Serialize};
use serde_json::Value;
/// The chat response format to be sent back by the LLM.
/// This will be taken into consideration only if the provider supports it.
///
/// > Note: Currently, the AI Providers will not report an error if not supported. It will just be ignored.
/// > This may change in the future.
#[derive(Debug, Clone, From, Serialize, Deserialize)]
pub enum ChatResponseFormat {
/// Request to return a well-formatted JSON. Mostly for OpenAI.
/// Note: Make sure to add "Reply in JSON format." to the prompt or system to ensure it works correctly.
JsonMode,
/// Request to return a stuctured Structured Output
#[from]
JsonSpec(JsonSpec),
}
/// The json specification for the Structured Output format.
#[derive(Debug, Clone, From, Serialize, Deserialize)]
pub struct JsonSpec {
/// The name of the spec. Mostly used by open-ai.
/// IMPORTANT: With openAI, this cannot contains any space or special characters beside `-` and `_`
pub name: String,
/// The description of the json spec. Mostly used by OpenAI adapters (future).
/// NOTE: Today, ignored in the OpenAI adapter
pub description: Option<String>,
/// The simplified json-schema that will be used by the AI Provider as json schema.
pub schema: Value,
}
/// Constructors
impl JsonSpec {
/// Create a new JsonSpec from name and schema.
pub fn new(name: impl Into<String>, schema: impl Into<Value>) -> Self {
Self {
name: name.into(),
@@ -29,6 +46,7 @@ impl JsonSpec {
/// Setters
impl JsonSpec {
/// Chainable setter to set the descrition in a JsonSpec construct.
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self

View File

@@ -14,11 +14,11 @@ pub struct ChatStream {
}
impl ChatStream {
pub fn new(inter_stream: InterStreamType) -> Self {
pub(crate) fn new(inter_stream: InterStreamType) -> Self {
ChatStream { inter_stream }
}
pub fn from_inter_stream<T>(inter_stream: T) -> Self
pub(crate) fn from_inter_stream<T>(inter_stream: T) -> Self
where
T: Stream<Item = crate::Result<InterStreamEvent>> + Send + Unpin + 'static,
{
@@ -55,18 +55,29 @@ impl Stream for ChatStream {
// region: --- ChatStreamEvent
/// The normalized chat stream event for any provider when calling `Client::exec`
#[derive(Debug, From, Serialize, Deserialize)]
pub enum ChatStreamEvent {
/// Represents the start of the stream. First event.
Start,
/// Represents each chunk response. Currently only contains text content.
Chunk(StreamChunk),
/// Represents the end of the stream.
/// Will have the `.captured_usage` and `.captured_content` if specified in the `ChatOptions`
End(StreamEnd),
}
/// Chunk content of the `ChatStreamEvent::Chunk` variant.
/// For now, just contain text
#[derive(Debug, Serialize, Deserialize)]
pub struct StreamChunk {
/// The content text
pub content: String,
}
/// StreamEnd content, with the eventual `.captured_usage` and `.captured_content`
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct StreamEnd {
/// The eventual captured UsageMeta

View File

@@ -4,11 +4,13 @@ use serde::{Deserialize, Serialize};
/// But the goal is to support multi-part message content (see below)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MessageContent {
/// Text content
Text(String),
}
/// Constructors
impl MessageContent {
/// Create a new MessageContent with Text variant
pub fn text(content: impl Into<String>) -> Self {
MessageContent::Text(content.into())
}

View File

@@ -1,3 +1,6 @@
//! The genai chat module contains all of the constructs necessary
//! to make genai requests with the `genai::Client`.
// region: --- Modules
mod chat_options;

View File

@@ -1,3 +1,6 @@
//! Printer utility to help print a chat stream
//! > Note: This is mostly for quick testing and temporary debugging
use crate::chat::{ChatStreamEvent, ChatStreamResponse, StreamChunk};
use futures::StreamExt;
use serde::{Deserialize, Serialize};
@@ -8,6 +11,7 @@ type Result<T> = core::result::Result<T, Error>;
// region: --- PrintChatOptions
/// Options to be passed into the `printer::print_chat_stream`
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct PrintChatStreamOptions {
print_events: Option<bool>,
@@ -15,6 +19,7 @@ pub struct PrintChatStreamOptions {
/// constructors
impl PrintChatStreamOptions {
/// Create a `PrintChatStreamOptions` with the `print_events` field set to `true`
pub fn from_print_events(print_events: bool) -> Self {
PrintChatStreamOptions {
print_events: Some(print_events),
@@ -114,6 +119,7 @@ use derive_more::From;
/// The Printer error.
#[derive(Debug, From)]
pub enum Error {
/// The `tokio::io::Error` when using `tokio::io::stdout`
#[from]
TokioIo(tokio::io::Error),
}

View File

@@ -1,6 +1,10 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
/// NOT USED FOR NOW
/// > For later, will be for function calling
/// > Will probably use the JsonSpec type we had in response format,
/// > or have a `From<JsonSpec>` implementation.
#[allow(unused)] // Not used yet
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tool {

View File

@@ -4,6 +4,10 @@ use crate::webc::WebClient;
use crate::{Client, ClientConfig};
use std::sync::Arc;
/// The builder for the `Client` construct.
///
/// - `ClientBuilder::default()`
/// - `Client::builder()`
#[derive(Debug, Default)]
pub struct ClientBuilder {
web_client: Option<WebClient>,
@@ -12,36 +16,39 @@ pub struct ClientBuilder {
/// Builder methods
impl ClientBuilder {
/// Create a new ClientBuilder with a custom `reqwest::Client`.
pub fn with_reqwest(mut self, reqwest_client: reqwest::Client) -> Self {
self.web_client = Some(WebClient::from_reqwest_client(reqwest_client));
self
}
/// With a client config.
/// With a client configuration.
pub fn with_config(mut self, config: ClientConfig) -> Self {
self.config = Some(config);
self
}
}
/// Builder ClientConfig passthrough convenient setters
/// The goal of those functions is to set nested value such as ClientConfig.
/// Builder ClientConfig passthrough convenient setters.
/// The goal of these functions is to set nested values such as ClientConfig.
impl ClientBuilder {
/// Set the ChatOptions for the ClientConfig of this ClientBuilder.
/// Will create the ClientConfig if not present.
/// Otherwise, will just set the `client_config.chat_options`
/// This will create the ClientConfig if it is not present.
/// Otherwise, it will just set the `client_config.chat_options`.
pub fn with_chat_options(mut self, options: ChatOptions) -> Self {
let client_config = self.config.get_or_insert_with(ClientConfig::default);
client_config.chat_options = Some(options);
self
}
/// Set the authentication resolver for the ClientConfig of this ClientBuilder.
pub fn with_auth_resolver(mut self, auth_resolver: AuthResolver) -> Self {
let client_config = self.config.get_or_insert_with(ClientConfig::default);
client_config.auth_resolver = Some(auth_resolver);
self
}
/// Set the authentication resolver function for the ClientConfig of this ClientBuilder.
pub fn with_auth_resolver_fn(mut self, auth_resolver_fn: impl IntoAuthResolverFn) -> Self {
let client_config = self.config.get_or_insert_with(ClientConfig::default);
let auth_resolver = AuthResolver::from_resolver_fn(auth_resolver_fn);
@@ -49,12 +56,14 @@ impl ClientBuilder {
self
}
/// Set the model mapper for the ClientConfig of this ClientBuilder.
pub fn with_model_mapper(mut self, model_mapper: ModelMapper) -> Self {
let client_config = self.config.get_or_insert_with(ClientConfig::default);
client_config.model_mapper = Some(model_mapper);
self
}
/// Set the model mapper function for the ClientConfig of this ClientBuilder.
pub fn with_model_mapper_fn(mut self, model_mapper_fn: impl IntoModelMapperFn) -> Self {
let client_config = self.config.get_or_insert_with(ClientConfig::default);
let model_mapper = ModelMapper::from_mapper_fn(model_mapper_fn);
@@ -63,8 +72,8 @@ impl ClientBuilder {
}
}
/// Build() method
impl ClientBuilder {
/// Build a new immutable GenAI client.
pub fn build(self) -> Client {
let inner = super::ClientInner {
web_client: self.web_client.unwrap_or_default(),
@@ -72,4 +81,4 @@ impl ClientBuilder {
};
Client { inner: Arc::new(inner) }
}
}
}

View File

@@ -76,6 +76,7 @@ impl Client {
Ok(chat_res)
}
/// Execute a chat stream response.
pub async fn exec_chat_stream(
&self,
model: &str,

View File

@@ -3,6 +3,10 @@ use crate::webc::WebClient;
use crate::ClientBuilder;
use std::sync::Arc;
/// genai Client for executing AI requests to any providers.
/// Build with:
/// - `ClientBuilder::default()...build()`
/// - or `Client::builder()`, which is equivalent to `ClientBuilder::default()...build()`
#[derive(Debug, Clone)]
pub struct Client {
pub(super) inner: Arc<ClientInner>,
@@ -17,6 +21,8 @@ impl Default for Client {
}
impl Client {
/// Create a new ClientBuilder for Client
/// Just another way for `ClientBuilder::default()`
pub fn builder() -> ClientBuilder {
ClientBuilder::default()
}

View File

@@ -1,8 +1,7 @@
use crate::chat::ChatOptions;
use crate::resolver::{AuthResolver, ModelMapper};
// Note: Here the properties are `(in crate::client)` to allow the Client builder to set those values
// with passthrough setters.
/// The Client configuration used in the configuration builder stage.
#[derive(Debug, Default, Clone)]
pub struct ClientConfig {
pub(in crate::client) auth_resolver: Option<AuthResolver>,
@@ -10,35 +9,40 @@ pub struct ClientConfig {
pub(in crate::client) chat_options: Option<ChatOptions>,
}
/// Adapter Related Chainable Setters
/// Adapter-related chainable setters for ClientConfig.
impl ClientConfig {
/// Set the AuthResolver for the ClientConfig.
pub fn with_auth_resolver(mut self, auth_resolver: AuthResolver) -> Self {
self.auth_resolver = Some(auth_resolver);
self
}
/// Set the ModelMapper for the ClientConfig.
pub fn with_model_mapper(mut self, model_mapper: ModelMapper) -> Self {
self.model_mapper = Some(model_mapper);
self
}
/// Default chat request options
/// Set the default chat request options for the ClientConfig.
pub fn with_chat_options(mut self, options: ChatOptions) -> Self {
self.chat_options = Some(options);
self
}
}
/// Getters (as ref/deref)
/// Getters for ClientConfig fields (as references).
impl ClientConfig {
/// Get a reference to the AuthResolver, if it exists.
pub fn auth_resolver(&self) -> Option<&AuthResolver> {
self.auth_resolver.as_ref()
}
/// Get a reference to the ModelMapper, if it exists.
pub fn model_mapper(&self) -> Option<&ModelMapper> {
self.model_mapper.as_ref()
}
/// Get a reference to the ChatOptions, if they exist.
pub fn chat_options(&self) -> Option<&ChatOptions> {
self.chat_options.as_ref()
}

View File

@@ -3,15 +3,20 @@ use serde::{Deserialize, Serialize};
use crate::adapter::AdapterKind;
use crate::ModelName;
/// Hold the adapter_kind and model_name in a efficient clonable way
/// Note: For now,
/// Holds the adapter kind and model name in an efficient, clonable way.
///
/// This struct is used to represent the association between an adapter kind
/// and a model name, allowing for easy conversion and instantiation.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ModelIden {
/// The adapter kind.
pub adapter_kind: AdapterKind,
/// The model name.
pub model_name: ModelName,
}
impl ModelIden {
/// Create a new `ModelIden` with the given adapter kind and model name.
pub fn new(adapter_kind: AdapterKind, model_name: impl Into<ModelName>) -> Self {
Self {
adapter_kind,

View File

@@ -3,6 +3,7 @@ use std::sync::Arc;
use serde::{Deserialize, Serialize};
/// The model name, which is just a `Arc<str>` wrapper (simple and relatively efficient to clone)
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ModelName(Arc<str>);

View File

@@ -4,9 +4,12 @@ use crate::{resolver, webc, ModelIden};
use derive_more::From;
use value_ext::JsonValueExtError;
/// genai main Result type alias (with genai::Error)
pub type Result<T> = core::result::Result<T, Error>;
/// Main genai error
#[derive(Debug, From)]
#[allow(missing_docs)]
pub enum Error {
// -- Chat Input
ChatReqHasNoMessages {

View File

@@ -1,3 +1,6 @@
//! genai library - any AI-Provider AI client library.
//! See [examples/c00-readme.rs](./examples/c00-readme.rs)
// region: --- Modules
mod client;

View File

@@ -1,10 +1,10 @@
//! An AuthResolver is responsible for returning the AuthData (typically containing the `api_key`).
//! An `AuthResolver` is responsible for returning the `AuthData` (typically containing the `api_key`).
//! It can take the following forms:
//! - Configured with a custom environment name,
//! - Contain a fixed auth value,
//! - Contain an `AuthResolverFn` trait object or closure that will be called to return the AuthData.
//! - Contain an `AuthResolverFn` trait object or closure that will be called to return the `AuthData`.
//!
//! Note: AuthData is typically a single value but can be Multi for future adapters (e.g., AWS Bedrock).
//! Note: `AuthData` is typically a single value but can be multi for future adapters (e.g., AWS Bedrock).
use crate::resolver::{Error, Result};
use crate::ModelIden;
@@ -13,14 +13,16 @@ use std::sync::Arc;
// region: --- AuthResolver
/// Holder for the AuthResolver function.
/// NOTE: Eventually, we might also have a `ResolveAsyncFn`, hence the enum
/// Holder for the `AuthResolver` function.
/// NOTE: Eventually, we might also have a `ResolveAsyncFn`, hence the enum.
#[derive(Debug, Clone)]
pub enum AuthResolver {
/// The `AuthResolverFn` trait object.
ResolverFn(Arc<Box<dyn AuthResolverFn>>),
}
impl AuthResolver {
/// Create a new `AuthResolver` from a resolver function.
pub fn from_resolver_fn(resolver_fn: impl IntoAuthResolverFn) -> Self {
AuthResolver::ResolverFn(resolver_fn.into_resolver_fn())
}
@@ -30,7 +32,7 @@ impl AuthResolver {
pub(crate) fn resolve(&self, model_iden: ModelIden) -> Result<Option<AuthData>> {
match self {
AuthResolver::ResolverFn(resolver_fn) => {
// Clone the Arc to get a new reference to the Box, then call exec_fn
// Clone the Arc to get a new reference to the Box, then call exec_fn.
resolver_fn.clone().exec_fn(model_iden)
}
}
@@ -41,13 +43,16 @@ impl AuthResolver {
// region: --- AuthResolverFn
// Define the trait for an auth resolver function
/// The `AuthResolverFn` trait object.
pub trait AuthResolverFn: Send + Sync {
/// Execute the `AuthResolverFn` to get the `AuthData`.
fn exec_fn(&self, model_iden: ModelIden) -> Result<Option<AuthData>>;
/// Clone the trait object.
fn clone_box(&self) -> Box<dyn AuthResolverFn>;
}
// Implement AuthResolverFn for any `FnOnce`
/// `AuthResolverFn` blanket implementation for any function that matches the `AuthResolver` function signature.
impl<F> AuthResolverFn for F
where
F: FnOnce(ModelIden) -> Result<Option<AuthData>> + Send + Sync + Clone + 'static,
@@ -78,7 +83,9 @@ impl std::fmt::Debug for dyn AuthResolverFn {
// region: --- IntoAuthResolverFn
/// Custom and convenient trait used in the `AuthResolver::from_resolver_fn` argument.
pub trait IntoAuthResolverFn {
/// Convert the argument into an `AuthResolverFn` trait object.
fn into_resolver_fn(self) -> Arc<Box<dyn AuthResolverFn>>;
}
@@ -88,7 +95,7 @@ impl IntoAuthResolverFn for Arc<Box<dyn AuthResolverFn>> {
}
}
// Implement IntoAuthResolverFn for closures
// Implement `IntoAuthResolverFn` for closures.
impl<F> IntoAuthResolverFn for F
where
F: FnOnce(ModelIden) -> Result<Option<AuthData>> + Send + Sync + Clone + 'static,
@@ -102,22 +109,34 @@ where
// region: --- AuthData
/// `AuthData` specifies either how or the key itself for an authentication resolver call.
#[derive(Clone)]
pub enum AuthData {
/// Specify the environment name to get the key value from.
FromEnv(String),
/// The key value itself.
Key(String),
/// The key names/values when a credential has multiple credential information.
/// This will be adapter-specific.
/// NOTE: Not used yet.
MultiKeys(HashMap<String, String>),
}
/// Constructors
impl AuthData {
/// Create a new `AuthData` from an environment variable name.
pub fn from_env(env_name: impl Into<String>) -> Self {
AuthData::FromEnv(env_name.into())
}
/// Create a new `AuthData` from a single value.
pub fn from_single(value: impl Into<String>) -> Self {
AuthData::Key(value.into())
}
/// Create a new `AuthData` from multiple values.
pub fn from_multi(data: HashMap<String, String>) -> Self {
AuthData::MultiKeys(data)
}
@@ -125,10 +144,11 @@ impl AuthData {
/// Getters
impl AuthData {
/// Get the single value from the `AuthData`.
pub fn single_value(&self) -> Result<String> {
match self {
AuthData::FromEnv(env_name) => {
// get value from env name
// Get value from environment name.
let value = std::env::var(env_name).map_err(|_| Error::ApiKeyEnvNotFound {
env_name: env_name.to_string(),
})?;
@@ -144,11 +164,11 @@ impl AuthData {
// region: --- AuthData Std Impls
// implement Debug to redact
// Implement Debug to redact sensitive information.
impl std::fmt::Debug for AuthData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
// NOTE: here we also redact for FromEnv in case dev confused this with key
// NOTE: Here we also redact for `FromEnv` in case the developer confuses this with a key.
AuthData::FromEnv(_env_name) => write!(f, "AuthData::FromEnv(REDACTED)"),
AuthData::Key(_) => write!(f, "AuthData::Single(REDACTED)"),
AuthData::MultiKeys(_) => write!(f, "AuthData::Multi(REDACTED)"),

View File

@@ -1,14 +1,21 @@
use derive_more::From;
/// resolver Result type alias
pub type Result<T> = core::result::Result<T, Error>;
/// Resolver error type
#[derive(Debug, From)]
pub enum Error {
/// The API key environment variable was not found.
ApiKeyEnvNotFound {
/// The name of the environment variable.
env_name: String,
},
/// The `AuthData` is not a single value.
ResolverAuthDataNotSingleValue,
/// Custom error message.
#[from]
Custom(String),
}

View File

@@ -1,7 +1,7 @@
// Resolvers are hooks that library users can set to customize part of the library's default behavior.
// A good example for now is the AuthResolver, which provides the authentication data (e.g., api_key).
//
// Eventually, the library will have more resolvers.
//! Resolvers are hooks that library users can set to customize part of the library's default behavior.
//! A good example for now is the AuthResolver, which provides the authentication data (e.g., api_key).
//!
//! Eventually, the library will have more resolvers.
// region: --- Modules

View File

@@ -8,10 +8,12 @@ use std::sync::Arc;
/// It must return a `ModelIden` or an appropriate
#[derive(Debug, Clone)]
pub enum ModelMapper {
/// The mapper function holder variant
MapperFn(Arc<Box<dyn ModelMapperFn>>),
}
impl ModelMapper {
/// Create a new `ModelMapper` from a mapper function.
pub fn from_mapper_fn(mapper_fn: impl IntoModelMapperFn) -> Self {
ModelMapper::MapperFn(mapper_fn.into_mapper_fn())
}
@@ -32,9 +34,12 @@ impl ModelMapper {
// region: --- ModelMapperFn
// Define the trait for an auth resolver function
/// The `ModelMapperFn` trait object.
pub trait ModelMapperFn: Send + Sync {
/// Execute the `ModelMapperFn` to get the `ModelIden`.
fn exec_fn(&self, model_iden: ModelIden) -> Result<ModelIden>;
/// Clone the trait object into a box dyn
fn clone_box(&self) -> Box<dyn ModelMapperFn>;
}
@@ -69,7 +74,9 @@ impl std::fmt::Debug for dyn ModelMapperFn {
// region: --- IntoModelMapperFn
/// Implement IntoModelMapperFn for closures `ModelMapper::from_mapper_fn` argument
pub trait IntoModelMapperFn {
/// Convert the given closure into a `ModelMapperFn` trait object.
fn into_mapper_fn(self) -> Arc<Box<dyn ModelMapperFn>>;
}
@@ -79,7 +86,6 @@ impl IntoModelMapperFn for Arc<Box<dyn ModelMapperFn>> {
}
}
// Implement IntoModelMapperFn for closures
impl<F> IntoModelMapperFn for F
where
F: FnOnce(ModelIden) -> Result<ModelIden> + Send + Sync + Clone + 'static,

View File

@@ -4,6 +4,8 @@ use value_ext::JsonValueExtError;
pub type Result<T> = core::result::Result<T, Error>;
/// webc sub module error.
#[allow(missing_docs)]
#[derive(Debug, From)]
pub enum Error {
ResponseFailedNotJson {

View File

@@ -1,3 +1,5 @@
//! The genai web client construct that use reqwest. Only `webc::Error` is exposed as public interface.
// region: --- Modules
mod error;