openai_client_cli/program/loaders/
key.rs

1use crate::{Entry, Error, Result, traits::*};
2use regex::Regex;
3use shellexpand::path::tilde;
4use std::{env, fs, path::{Path, PathBuf}, str::FromStr};
5use tracing::{debug, info};
6
7/// The API key.
8pub struct Key(String);
9
10impl Key {
11  fn post_fetch_ok(self, source: &str) -> Result<Self> {
12    info!(
13      "Successfully fetched the API key from {source}: {:?}",
14      format!("{}...{}", &self.value_ref()[..6], &self.value_ref()[49..]),
15    );
16    Ok(self)
17  }
18}
19
20impl FromFile for Key {
21  fn from_file<P>(path: P) -> Result<Self>
22  where
23    P: AsRef<Path>,
24  {
25    let text = fs::read_to_string(path)?;
26    Self::from_str(&text)
27  }
28}
29
30impl FromStr for Key {
31  type Err = Error;
32
33  fn from_str(text: &str) -> Result<Self> {
34    Ok(Self(
35      Regex::new(r"sk-[[:alnum:]]{20}T3BlbkFJ[[:alnum:]]{20}")?
36        .find(text)
37        .ok_or(Error::msg("Invalid format of OpenAI API key"))?
38        .as_str()
39        .to_string()
40    ))
41  }
42}
43
44impl Loader<String> for Key {
45  fn fetch(entry: &Entry) -> Result<Self> {
46    if let Some(provided_file) = entry.key_file.as_ref() {
47      let source = &format!("the provided file {provided_file:?}");
48      match Key::from_file(provided_file) {
49        Ok(key) => return key.post_fetch_ok(source),
50        Err(err) => debug!("Failed to obtain the API key from {source}: {err:?}"),
51      }
52    }
53
54    let source = "the environment variable `OPENAI_API_KEY`";
55    match env::var("OPENAI_API_KEY")
56      .map_err(Error::from)
57      .and_then(Key::try_from)
58    {
59      Ok(key) => return key.post_fetch_ok(source),
60      Err(err) => debug!("Failed to obtain the API key from {source}: {err:?}"),
61    }
62
63    for default_file in [
64        &PathBuf::from("openai.env"),
65        &PathBuf::from(".openai_profile"),
66        &PathBuf::from(".env"),
67        &PathBuf::from(tilde("~/openai.env")),
68        &PathBuf::from(tilde("~/.openai_profile")),
69        &PathBuf::from(tilde("~/.env")),
70      ].into_iter()
71    {
72      let source = &format!("the default file {default_file:?}");
73      match Key::from_file(default_file) {
74        Ok(key) => return key.post_fetch_ok(source),
75        Err(err) => debug!("Failed to obtain the API key from {source}: {err:?}"),
76      }
77    }
78    Err(Error::msg("Failed to fetch the API key"))
79  }
80  fn value(self) -> String {
81    self.0
82  }
83  fn value_ref(&self) -> &String {
84    &self.0
85  }
86}
87
88impl TryFrom<&str> for Key {
89  type Error = Error;
90
91  fn try_from(text: &str) -> Result<Self> {
92    Self::from_str(text)
93  }
94}
95
96impl TryFrom<String> for Key {
97  type Error = Error;
98
99  fn try_from(text: String) -> Result<Self> {
100    Self::from_str(&text)
101  }
102}
103
104impl TryFrom<&String> for Key {
105  type Error = Error;
106
107  fn try_from(text: &String) -> Result<Self> {
108    Self::from_str(text)
109  }
110}