1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
//! REST API client module for OpenAI interface
//!
//! This module provides the core HTTP functionality for making requests to OpenAI-compatible APIs.
//! It includes traits and implementations for both streaming and non-streaming API calls.
//!
//! # Overview
//!
//! The `rest` module contains:
//! - [`post`]: HTTP POST request functionality with streaming and non-streaming support
//! - [`get`]: HTTP GET request functionality with various parameter handling options
//! - [`delete`]: HTTP DELETE request functionality
//! - [`default_client`]: A `reqwest::Client` constructor shared by all request traits
//! - [`check_status`]: Shared non-2xx response handling which parses the error body
//!
//! # Usage
//!
//! The module is designed to be used through the higher-level API modules (`chat`, `completions`,
//! etc.). However, you can use the traits directly if needed:
//!
//! ## POST Requests
//!
//! ```rust
//! use openai_interface::rest::post::{Post, PostNoStream};
//! use openai_interface::errors::OapiError;
//! use serde::{Serialize, Deserialize};
//!
//! use std::str::FromStr;
//!
//! #[derive(Serialize)]
//! struct MyRequest {
//! prompt: String,
//! stream: bool,
//! }
//!
//! #[derive(Deserialize)]
//! struct MyResponse {
//! // Define the fields of your response here
//! id: String,
//! }
//!
//! impl FromStr for MyResponse {
//! type Err = OapiError;
//!
//! fn from_str(content: &str) -> Result<Self, Self::Err> {
//! let parse_result: Result<Self, _> = serde_json::from_str(content)
//! .map_err(|e| OapiError::DeserializationError(e.to_string()));
//! parse_result
//! }
//! }
//!
//! impl Post for MyRequest {
//! fn is_streaming(&self) -> bool {
//! self.stream
//! }
//! fn build_url(&self, base_url: &str) -> Result<String, OapiError> {
//! Ok(format!("{}/service", base_url))
//! }
//! }
//!
//! impl PostNoStream for MyRequest {
//! type Response = MyResponse;
//! }
//!
//! // Send it with a client:
//! // let client = openai_interface::rest::default_client();
//! // let response: MyResponse = request
//! // .get_response(&client, "https://api.openai.com/v1/chat/completions", "API_KEY")
//! // .await?;
//! ```
//!
//! ## GET Requests
//!
//! ```rust
//! use openai_interface::rest::get::Get;
//! use openai_interface::errors::OapiError;
//!
//! // GET request with URL building
//! struct ComplexRequest {
//! resource_id: String,
//! limit: Option<u32>,
//! }
//!
//! impl Get for ComplexRequest {
//! fn build_url(&self, base_url: &str) -> Result<String, OapiError> {
//! let mut url = format!("{}/{}", base_url, self.resource_id);
//! if let Some(limit) = self.limit {
//! url.push_str(&format!("?limit={}", limit));
//! }
//! Ok(url)
//! }
//! }
//! ```
//!
//! # Client configuration
//!
//! Every request method takes the client as its first argument, so callers
//! control proxies, timeouts and connection pooling. Use
//! [`default_client`] for a sensible default, or build your own, e.g. with a
//! proxy:
//!
//! ```rust
//! # #[cfg(feature = "ferritls")] {
//! // Building a client needs an installed provider: this one comes from the
//! // `ferritls` feature (see "TLS crypto provider" below).
//! openai_interface::rest::install_crypto_provider().ok();
//! let client = reqwest::Client::builder()
//! .proxy(reqwest::Proxy::http("http://127.0.0.1:10808")?)
//! .timeout(std::time::Duration::from_secs(60))
//! .build()?;
//! # }
//! # Ok::<(), reqwest::Error>(())
//! ```
//!
//! # TLS crypto provider
//!
//! This crate depends on reqwest with its `rustls-no-provider` feature, so
//! the rustls stack is compiled **without** a crypto backend. That leaves the
//! choice of backend to the application: exactly one
//! [`rustls::crypto::CryptoProvider`] must be installed as the process
//! default before any `reqwest::Client` is built, otherwise reqwest panics at
//! construction time.
//!
//! Nothing in this crate installs a provider for you — neither
//! [`default_client`] nor any request method touches the global state, so an
//! application that picked a provider first keeps it, and an application that
//! never builds a client through this crate is free to install its own
//! whenever it likes.
//!
//! This example is the regression lock for that promise. It runs as
//! `should_panic`, deliberately with no provider installed and no hidden
//! `ferritls` setup: if `default_client()` ever learns to install one on the
//! side, it stops panicking and `cargo test` fails.
//!
//! ```rust,should_panic
//! # // Silence reqwest's expected panic message so `--nocapture` stays clean;
//! # // the payload still propagates, so should_panic matches as usual.
//! # std::panic::set_hook(Box::new(|_| {}));
//! let _client = openai_interface::rest::default_client();
//! ```
//!
//! ## With the `ferritls` feature
//!
//! The optional `ferritls` feature (off by default) adds the pure-Rust
//! `ferritls-rustls` backend and the [`install_crypto_provider`] helper.
//! Enable it when you are happy to let this crate pick a provider for you:
//!
//! ```toml
//! [dependencies]
//! openai-interface = { version = "0.10", features = ["ferritls"] }
//! ```
//!
//! Then call it once at startup, before the first client:
//!
//! ```rust
//! # #[cfg(feature = "ferritls")] {
//! openai_interface::rest::install_crypto_provider()
//! .expect("a rustls crypto provider was already installed");
//! let client = openai_interface::rest::default_client();
//! # }
//! ```
//!
//! ## Without it
//!
//! With the feature off, `ferritls-rustls` is not in the dependency tree at
//! all and [`install_crypto_provider`] does not exist. Install a provider
//! yourself instead — first install wins, so do it before any client is
//! built:
//!
//! ```rust,ignore
//! // In the application crate, with `rustls = "0.23"` (feature `ring` or
//! // `aws-lc-rs`) as one of its own dependencies:
//! rustls::crypto::ring::default_provider()
//! .install_default()
//! .expect("a rustls crypto provider was already installed");
//! ```
//!
//! ## When reqwest already has a backend
//!
//! Because Cargo features are additive, a project that depends on `reqwest`
//! itself with `default-tls` / `rustls` (its defaults, which fall back to the
//! bundled `aws-lc-rs` provider) or with `native-tls` (which skips the rustls
//! path entirely) needs no provider installed here at all. That backend is
//! then picked by feature unification instead of by you; see the
//! ["TLS Crypto Provider"][crate#when-nothing-needs-to-be-installed] section
//! of the crate docs for the trade-off.
use crate;
/// Installs the pure-Rust [`ferritls-rustls`](https://crates.io/crates/ferritls-rustls)
/// crypto provider as the process-wide default for rustls.
///
/// Only compiled with the `ferritls` cargo feature, which is what puts
/// `ferritls-rustls` in the dependency tree at all; without the feature this
/// crate ships no crypto backend, so you either install a
/// [`rustls::crypto::CryptoProvider`] of your own or depend on `reqwest`
/// yourself with a backend compiled in — see
/// [When reqwest already has a backend](self#when-reqwest-already-has-a-backend).
/// (`doc` builds include this function anyway so its entry and the links to it
/// exist regardless of features.)
///
/// This crate depends on reqwest with the `rustls-no-provider` feature, so
/// no crypto backend is compiled in by default and building a
/// [`reqwest::Client`] without an installed provider panics. This helper lets
/// an application delegate that choice to the crate; it is **never called
/// implicitly** — no function here mutates the global provider on its own.
///
/// First install wins: if any crate (including the application itself) got
/// there first, that provider is kept and the `Err` variant carries it. Call
/// this once at startup, before the first client is built.
///
/// # Errors
///
/// Never fails; the `Err` variant carries the existing provider when one is
/// already installed.
/// Builds a [`reqwest::Client`] with library defaults.
///
/// The client has a 300-second total timeout and a 60-second connect
/// timeout. Pass your own client to any request method if you need a
/// different configuration (proxy, timeout, pooling, ...).
///
/// This function does not select a TLS backend: see the
/// [module docs][self#tls-crypto-provider] for why, and install a provider
/// (with [`install_crypto_provider`] under the `ferritls` feature, or one of
/// your own) before the first client is built.
///
/// # Panics
///
/// Panics if no rustls crypto provider has been installed as the process
/// default yet — reqwest's `rustls-no-provider` build requires one at
/// client construction time. This mirrors the panic behavior of
/// [`reqwest::Client::new`].
/// Checks a response status, turning a non-2xx response into an
/// [`OapiError::ApiError`] that carries the parsed error body.
///
/// For a non-2xx status, this consumes the response and attempts to parse
/// the body as an [`ApiError`]. If the body cannot be parsed, the raw text
/// is kept as the error message. The response is returned unchanged when
/// the status is a success.
pub async
/// Parses a response body as text, applying [`check_status`] first.
pub async
/// Parses a response body as raw bytes, applying [`check_status`] first.
pub async