zero_sdk/lib.rs
1use std::collections::HashMap;
2mod json;
3mod tests;
4
5/// Zero API client. Instantiate with a token, than call the `.fetch()` method to download secrets.
6pub struct Zero {
7 api_url: String,
8 caller_name: Option<String>,
9 pick: Vec<String>,
10 token: String,
11}
12
13/// Constructor arguments. Defines required and optional params.
14pub struct Arguments {
15 pub token: String,
16 pub pick: Option<Vec<String>>,
17 pub caller_name: Option<String>,
18}
19
20/// The main client for accessing Zero GraphQL API.
21///
22/// ### Example:
23/// ```rust
24/// use zero_sdk::{Zero, Arguments};
25///
26/// let client = Zero::new(Arguments {
27/// pick: Some(vec![String::from("my-secret")]),
28/// token: String::from("my-zero-token"),
29/// caller_name: None,
30/// })
31/// .unwrap();
32/// ```
33impl Zero {
34 /// Set the URL which will be called in fetch(). The method was added mostly for convenience of testing.
35 pub fn set_api_url(mut self, new_api_url: String) -> Self {
36 self.api_url = new_api_url;
37 return self;
38 }
39
40 // TODO Implement proper error structures with a message and a code
41 // TODO Accepts an array of secrets to fetch
42 /// Fetch the secrets assigned to the token.
43 pub fn fetch(self) -> Result<HashMap<String, HashMap<String, String>>, String> {
44 let response = if let Ok(value) = ureq::post(&self.api_url).send_json(serde_json::json!({
45 "query":
46 format!(
47 "query {{
48 secrets(zeroToken: \"{}\", pick: [{}]{}) {{
49 name
50 fields {{
51 name value
52 }}
53 }}
54 }}",
55 &self.token,
56 &self
57 .pick
58 .iter()
59 .map(|secret| format!("\"{}\"", &secret))
60 .collect::<Vec<String>>()
61 .join(", "),
62
63 // REVIEW There should be a better way for string interpolation
64 if self.caller_name.is_some() {
65 format!(", callerName: \"{}\"", &self.caller_name.unwrap_or_default())
66 } else {
67 "".to_string()
68 },
69 )
70 })) {
71 value
72 } else {
73 return Err(String::from("Failed to fetch secrets due to network issue"));
74 };
75
76 let response_json = if let Ok(value) = response.into_json::<json::ResponseJson>() {
77 value
78 } else {
79 return Err(String::from("Server returned invalid response"));
80 };
81
82 if response_json.errors.is_some() {
83 return Err(String::from(&response_json.errors.unwrap()[0].message));
84 }
85
86 if response_json.data.is_none() {
87 return Err(String::from(
88 "Server returned invalid response (no secrets)",
89 ));
90 }
91
92 // Tranform response to the following structure:
93 // {nameOfTheSecret: {fieldOne: "fieldOneValue", fieldTwo: "fieldTwoValue"}}
94 Ok(response_json
95 .data
96 .unwrap()
97 .secrets
98 .unwrap()
99 .iter()
100 .map(|secret| {
101 (
102 secret.name.to_owned(),
103 HashMap::from_iter(
104 secret
105 .fields
106 .iter()
107 .map(|field| (field.name.to_owned(), field.value.to_owned())),
108 ),
109 )
110 })
111 .collect())
112 }
113
114 /// Instantiate new Zero struct. Requires token string to be non empty, other params are optional.
115 pub fn new(arguments: Arguments) -> Result<Self, &'static str> {
116 if arguments.token == "" {
117 return Err("Zero-token is empty");
118 }
119
120 Ok(Self {
121 api_url: String::from("https://core.tryzero.com/graphql"),
122 caller_name: arguments.caller_name,
123 pick: arguments.pick.unwrap_or(vec![]),
124 token: arguments.token,
125 })
126 }
127}