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}