Skip to content

Commit 650f5c0

Browse files
fix: aws parameter bugs (#688)
1 parent 906bf76 commit 650f5c0

File tree

8 files changed

+73
-26
lines changed

8 files changed

+73
-26
lines changed

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ anstream = "0.6.13"
3232
anyhow = "1.0.75"
3333
appkit-nsworkspace-bindings = { path = "crates/macos-utils/appkit-nsworkspace-bindings" }
3434
async-trait = "0.1.74"
35-
aws-types = "1.2.0"
35+
aws-smithy-runtime-api = "1.6.1"
36+
aws-smithy-types = "1.2.10"
37+
aws-types = "1.3.0"
3638
base64 = "0.22.1"
3739
block2 = "0.5.1"
3840
bytes = "1.5.0"
@@ -48,6 +50,7 @@ clap = { version = "4.5.23", features = [
4850
"wrap_help",
4951
] }
5052
cocoa = "0.26.0"
53+
convert_case = "0.6.0"
5154
core-foundation = "0.10.0"
5255
core-foundation-sys = "0.8.7"
5356
core-graphics = "0.24.0"

crates/fig_api_client/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ amzn-qdeveloper-streaming-client.workspace = true
2222
aws-config = "1.0.3"
2323
aws-credential-types = "1.0.3"
2424
aws-smithy-async = "1.2.2"
25-
aws-smithy-runtime-api = "1.6.1"
26-
aws-smithy-types = "1.2.10"
27-
aws-types = "1.3.0"
25+
aws-smithy-runtime-api.workspace = true
26+
aws-smithy-types.workspace = true
27+
aws-types.workspace = true
2828
bytes.workspace = true
2929
fig_auth.workspace = true
3030
fig_aws_common.workspace = true

crates/fig_aws_common/Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ license.workspace = true
99

1010
[dependencies]
1111
aws-runtime = "1.4.4"
12-
aws-smithy-runtime-api = "1.6.1"
13-
aws-smithy-types = "1.2.10"
14-
aws-types = "1.3.0"
15-
http = "0.2.12"
12+
aws-smithy-runtime-api.workspace = true
13+
aws-smithy-types.workspace = true
14+
aws-types.workspace = true
15+
http.workspace = true
1616
tracing.workspace = true
1717

1818
[dev-dependencies]

crates/fig_aws_common/src/user_agent_override_interceptor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ impl Intercept for UserAgentOverrideInterceptor {
126126
)?;
127127

128128
let headers = context.request_mut().headers_mut();
129-
headers.insert(USER_AGENT, ua.aws_ua_header());
129+
headers.insert(USER_AGENT.as_str(), ua.aws_ua_header());
130130
Ok(())
131131
}
132132
}

crates/q_cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ clap_complete = "4.5.39"
3434
clap_complete_fig = "4.4.0"
3535
color-eyre = "0.6.2"
3636
color-print = "0.3.5"
37+
convert_case.workspace = true
3738
crossterm = { version = "0.28.1", features = ["event-stream"] }
3839
ctrlc = "3.2.5"
3940
dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }

crates/q_cli/src/cli/chat/tools/tool_index.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
},
7676
{
7777
"name": "use_aws",
78-
"description": "Make an AWS CLI api call with the specified service, operation, and parameters. The arguments MUST conform to the AWS CLI specification.",
78+
"description": "Make an AWS CLI api call with the specified service, operation, and parameters. All arguments MUST conform to the AWS CLI specification.",
7979
"input_schema": {
8080
"type": "object",
8181
"properties": {
@@ -89,7 +89,7 @@
8989
},
9090
"parameters": {
9191
"type": "object",
92-
"description": "The parameters for the operation. Each parameter must be encoded as a string."
92+
"description": "The parameters for the operation. The parameter keys MUST conform to the AWS CLI specification. You should prefer to use JSON Syntax over shorthand syntax wherever possible."
9393
},
9494
"region": {
9595
"type": "string",

crates/q_cli/src/cli/chat/tools/use_aws.rs

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ use std::io::Write;
33
use std::process::Stdio;
44

55
use bstr::ByteSlice;
6+
use convert_case::{
7+
Case,
8+
Casing,
9+
};
610
use crossterm::{
711
queue,
812
style,
@@ -19,23 +23,23 @@ use super::{
1923
OutputKind,
2024
};
2125

22-
const ALLOWED_OPS: [&str; 6] = ["get", "describe", "list", "ls", "search", "batch_get"];
26+
const READONLY_OPS: [&str; 6] = ["get", "describe", "list", "ls", "search", "batch_get"];
2327

2428
// TODO: we should perhaps composite this struct with an interface that we can use to mock the
2529
// actual cli with. That will allow us to more thoroughly test it.
2630
#[derive(Debug, Deserialize)]
2731
pub struct UseAws {
2832
pub service_name: String,
2933
pub operation_name: String,
30-
pub parameters: Option<HashMap<String, String>>,
34+
pub parameters: Option<HashMap<String, serde_json::Value>>,
3135
pub region: String,
3236
pub profile_name: Option<String>,
3337
pub label: Option<String>,
3438
}
3539

3640
impl UseAws {
3741
pub fn requires_consent(&self) -> bool {
38-
!ALLOWED_OPS.iter().any(|op| self.operation_name.starts_with(op))
42+
!READONLY_OPS.iter().any(|op| self.operation_name.starts_with(op))
3943
}
4044

4145
pub async fn invoke(&self, _ctx: &Context, _updates: impl Write) -> Result<InvokeOutput> {
@@ -45,15 +49,9 @@ impl UseAws {
4549
command.arg("--profile").arg(profile_name);
4650
}
4751
command.arg(&self.service_name).arg(&self.operation_name);
48-
if let Some(parameters) = &self.parameters {
49-
for (param_name, val) in parameters {
50-
// Model might sometimes give parameters capitalized.
51-
let param_name = param_name.to_lowercase();
52-
if param_name.starts_with("--") {
53-
command.arg(param_name).arg(val);
54-
} else {
55-
command.arg(format!("--{}", param_name)).arg(val);
56-
}
52+
if let Some(parameters) = self.cli_parameters() {
53+
for (name, val) in parameters {
54+
command.arg(name).arg(val);
5755
}
5856
}
5957
let output = command
@@ -87,11 +85,11 @@ impl UseAws {
8785
style::Print("Running aws cli command:\n"),
8886
style::Print(format!("Service name: {}\n", self.service_name)),
8987
style::Print(format!("Operation name: {}\n", self.operation_name)),
90-
style::Print("Parameters: \n".to_string()),
9188
)?;
9289
if let Some(parameters) = &self.parameters {
90+
queue!(updates, style::Print("Parameters: \n".to_string()))?;
9391
for (name, value) in parameters {
94-
queue!(updates, style::Print(format!("{}: {}\n", name, value)))?;
92+
queue!(updates, style::Print(format!("- {}: {}\n", name, value)))?;
9593
}
9694
}
9795

@@ -112,6 +110,22 @@ impl UseAws {
112110
pub async fn validate(&mut self, _ctx: &Context) -> Result<()> {
113111
Ok(())
114112
}
113+
114+
/// Returns the CLI arguments properly formatted as kebab case if parameters is
115+
/// [Option::Some], otherwise None
116+
fn cli_parameters(&self) -> Option<Vec<(String, String)>> {
117+
if let Some(parameters) = &self.parameters {
118+
let mut params = vec![];
119+
for (param_name, val) in parameters {
120+
let param_name = format!("--{}", param_name.trim_start_matches("--").to_case(Case::Kebab));
121+
let param_val = val.as_str().map(|s| s.to_string()).unwrap_or(val.to_string());
122+
params.push((param_name, param_val));
123+
}
124+
Some(params)
125+
} else {
126+
None
127+
}
128+
}
115129
}
116130

117131
#[cfg(test)]
@@ -152,6 +166,34 @@ mod tests {
152166
assert!(cmd.requires_consent());
153167
}
154168

169+
#[test]
170+
fn test_use_aws_deser() {
171+
let cmd = use_aws! {{
172+
"service_name": "s3",
173+
"operation_name": "put-object",
174+
"parameters": {
175+
"TableName": "table-name",
176+
"KeyConditionExpression": "PartitionKey = :pkValue"
177+
},
178+
"region": "us-west-2",
179+
"profile_name": "default",
180+
"label": ""
181+
}};
182+
let params = cmd.cli_parameters().unwrap();
183+
assert!(
184+
params.iter().any(|p| p.0 == "--table-name" && p.1 == "table-name"),
185+
"not found in {:?}",
186+
params
187+
);
188+
assert!(
189+
params
190+
.iter()
191+
.any(|p| p.0 == "--key-condition-expression" && p.1 == "PartitionKey = :pkValue"),
192+
"not found in {:?}",
193+
params
194+
);
195+
}
196+
155197
#[tokio::test]
156198
#[ignore = "not in ci"]
157199
async fn test_aws_read_only() {

0 commit comments

Comments
 (0)