Skip to main content

openai_interface/rest/
options.rs

1//! Per-request authentication and header options.
2//!
3//! Every request method takes a [`RequestOptions`] value, which decides how
4//! the request authenticates and which extra headers are attached. The
5//! default is no authentication; [`RequestOptions::bearer`] reproduces the
6//! classic `Authorization: Bearer <token>` behavior of OpenAI and most
7//! compatible providers.
8//!
9//! # Examples
10//!
11//! Plain OpenAI-style bearer auth:
12//!
13//! ```
14//! use openai_interface::rest::RequestOptions;
15//!
16//! let options = RequestOptions::bearer("sk-...");
17//! # assert!(matches!(options.auth, openai_interface::rest::Auth::Bearer(_)));
18//! ```
19//!
20//! Azure OpenAI authenticates with an `api-key` header instead:
21//!
22//! ```
23//! use openai_interface::rest::RequestOptions;
24//!
25//! let options = RequestOptions::new()
26//!     .with_header("api-key", "...")
27//!     .unwrap();
28//! ```
29//!
30//! Anthropic uses `x-api-key` plus a version header:
31//!
32//! ```
33//! use openai_interface::rest::RequestOptions;
34//!
35//! let options = RequestOptions::new()
36//!     .with_header("x-api-key", "...")
37//!     .unwrap()
38//!     .with_header("anthropic-version", "2023-06-01")
39//!     .unwrap();
40//! ```
41//!
42//! Extra headers may also be layered on top of bearer auth, e.g. the
43//! `OpenAI-Organization` header:
44//!
45//! ```
46//! use openai_interface::rest::RequestOptions;
47//!
48//! let options = RequestOptions::bearer("sk-...")
49//!     .with_header("OpenAI-Organization", "org-...")
50//!     .unwrap();
51//! ```
52
53use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
54
55use crate::errors::OapiError;
56
57/// How a request authenticates itself.
58#[derive(Debug, Clone, Default, PartialEq, Eq)]
59pub enum Auth {
60    /// Send no authentication header at all. Useful when the credentials
61    /// travel in an extra header set via
62    /// [`RequestOptions::with_header`] (Azure `api-key`, Anthropic
63    /// `x-api-key`, ...), or for unauthenticated endpoints.
64    #[default]
65    None,
66    /// The classic `Authorization: Bearer <token>` header used by OpenAI and
67    /// most compatible providers.
68    Bearer(String),
69}
70
71/// Per-request authentication and header options.
72///
73/// Passed to every request method instead of a bare API key. Authentication
74/// headers ([`Auth`]) are applied first; [`RequestOptions::extra_headers`]
75/// are applied afterwards, so they can supply additional credentials of
76/// their own.
77#[derive(Debug, Clone, Default)]
78pub struct RequestOptions {
79    /// How the request authenticates itself. Defaults to [`Auth::None`].
80    pub auth: Auth,
81    /// Additional headers attached verbatim to the request, e.g.
82    /// `OpenAI-Organization`, Azure `api-key` or Anthropic
83    /// `anthropic-version`.
84    pub extra_headers: HeaderMap,
85}
86
87impl RequestOptions {
88    /// Creates options without authentication and without extra headers.
89    #[must_use]
90    pub fn new() -> Self {
91        Self::default()
92    }
93
94    /// Creates options authenticating with `Authorization: Bearer <token>`,
95    /// the scheme used by OpenAI and most compatible providers.
96    #[must_use]
97    pub fn bearer(token: impl Into<String>) -> Self {
98        Self {
99            auth: Auth::Bearer(token.into()),
100            extra_headers: HeaderMap::new(),
101        }
102    }
103
104    /// Adds an extra header, e.g. `api-key` for Azure or `x-api-key` plus
105    /// `anthropic-version` for Anthropic.
106    ///
107    /// Header names and values are validated eagerly; use
108    /// [`RequestOptions::extra_headers`] directly if you already hold typed
109    /// [`HeaderName`]/[`HeaderValue`]s.
110    ///
111    /// # Errors
112    ///
113    /// Returns [`OapiError::InvalidHeader`] if the name or value is not a
114    /// valid HTTP header.
115    pub fn with_header(mut self, name: &str, value: &str) -> Result<Self, OapiError> {
116        let name = HeaderName::from_bytes(name.as_bytes())
117            .map_err(|e| OapiError::InvalidHeader(format!("{name}: {e}")))?;
118        let value = HeaderValue::from_str(value)
119            .map_err(|e| OapiError::InvalidHeader(format!("{name}: {e}")))?;
120        self.extra_headers.insert(name, value);
121        Ok(self)
122    }
123}