Skip to content

Commit c31d477

Browse files
committed
cli: share HTTP helpers and improve schema guidance
1 parent 443e1b8 commit c31d477

File tree

10 files changed

+97
-84
lines changed

10 files changed

+97
-84
lines changed

.github/workflows/release-cli-npm.yml

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -122,25 +122,7 @@ jobs:
122122
fi
123123
VERSION="${VERSION#v}"
124124
echo "Building yaak version: $VERSION"
125-
python - "$VERSION" <<'PY'
126-
import pathlib
127-
import re
128-
import sys
129-
130-
version = sys.argv[1]
131-
manifest = pathlib.Path("crates-cli/yaak-cli/Cargo.toml")
132-
contents = manifest.read_text()
133-
updated, replacements = re.subn(
134-
r'(?m)^version = ".*"$',
135-
f'version = "{version}"',
136-
contents,
137-
count=1,
138-
)
139-
if replacements != 1:
140-
raise SystemExit("Failed to update yaak-cli version in Cargo.toml")
141-
manifest.write_text(updated)
142-
print(f"Updated {manifest} to version {version}")
143-
PY
125+
echo "YAAK_CLI_VERSION=$VERSION" >> "$GITHUB_ENV"
144126
145127
- name: Build yaak
146128
run: cargo build --locked --release -p yaak-cli --bin yaak --target ${{ matrix.target }}

crates-cli/yaak-cli/src/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::path::PathBuf;
44
#[derive(Parser)]
55
#[command(name = "yaak")]
66
#[command(about = "Yaak CLI - API client from the command line")]
7-
#[command(version)]
7+
#[command(version = crate::version::cli_version())]
88
pub struct Cli {
99
/// Use a custom data directory
1010
#[arg(long, global = true)]

crates-cli/yaak-cli/src/commands/auth.rs

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::cli::{AuthArgs, AuthCommands};
22
use crate::ui;
3+
use crate::utils::http;
34
use base64::Engine as _;
45
use keyring::Entry;
56
use rand::RngCore;
@@ -136,10 +137,8 @@ async fn whoami() -> CommandResult {
136137
};
137138

138139
let url = format!("{}/api/v1/whoami", environment.api_base_url());
139-
let response = reqwest::Client::new()
140+
let response = http::build_client(Some(&token))?
140141
.get(url)
141-
.header("X-Yaak-Session", token)
142-
.header(reqwest::header::USER_AGENT, user_agent())
143142
.send()
144143
.await
145144
.map_err(|e| format!("Failed to call whoami endpoint: {e}"))?;
@@ -156,7 +155,7 @@ async fn whoami() -> CommandResult {
156155
.to_string(),
157156
);
158157
}
159-
return Err(parse_api_error(status.as_u16(), &body));
158+
return Err(http::parse_api_error(status.as_u16(), &body));
160159
}
161160

162161
println!("{body}");
@@ -342,9 +341,8 @@ async fn write_redirect(stream: &mut TcpStream, location: &str) -> std::io::Resu
342341
}
343342

344343
async fn exchange_access_token(oauth: &OAuthFlow, code: &str) -> CommandResult<String> {
345-
let response = reqwest::Client::new()
344+
let response = http::build_client(None)?
346345
.post(&oauth.token_url)
347-
.header(reqwest::header::USER_AGENT, user_agent())
348346
.form(&[
349347
("grant_type", "authorization_code"),
350348
("client_id", OAUTH_CLIENT_ID),
@@ -406,38 +404,12 @@ fn delete_auth_token(environment: Environment) -> CommandResult {
406404
}
407405
}
408406

409-
fn parse_api_error(status: u16, body: &str) -> String {
410-
if let Ok(value) = serde_json::from_str::<Value>(body) {
411-
if let Some(message) = value.get("message").and_then(Value::as_str) {
412-
return message.to_string();
413-
}
414-
if let Some(error) = value.get("error").and_then(Value::as_str) {
415-
return error.to_string();
416-
}
417-
}
418-
419-
format!("API error {status}: {body}")
420-
}
421-
422407
fn random_hex(bytes: usize) -> String {
423408
let mut data = vec![0_u8; bytes];
424409
OsRng.fill_bytes(&mut data);
425410
hex::encode(data)
426411
}
427412

428-
fn user_agent() -> String {
429-
format!("YaakCli/{} ({})", env!("CARGO_PKG_VERSION"), ua_platform())
430-
}
431-
432-
fn ua_platform() -> &'static str {
433-
match std::env::consts::OS {
434-
"windows" => "Win",
435-
"darwin" => "Mac",
436-
"linux" => "Linux",
437-
_ => "Unknown",
438-
}
439-
}
440-
441413
fn confirm_open_browser() -> CommandResult<bool> {
442414
if !io::stdin().is_terminal() {
443415
return Ok(true);

crates-cli/yaak-cli/src/commands/plugin.rs

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use crate::cli::{GenerateArgs, PluginArgs, PluginCommands, PluginPathArg};
22
use crate::ui;
3+
use crate::utils::http;
34
use keyring::Entry;
45
use rand::Rng;
56
use rolldown::{
67
Bundler, BundlerOptions, ExperimentalOptions, InputItem, LogLevel, OutputFormat, Platform,
78
WatchOption, Watcher,
89
};
910
use serde::Deserialize;
10-
use serde_json::Value;
1111
use std::collections::HashSet;
1212
use std::fs;
1313
use std::io::{self, IsTerminal, Read, Write};
@@ -186,10 +186,8 @@ async fn publish(args: PluginPathArg) -> CommandResult {
186186

187187
ui::info("Uploading plugin");
188188
let url = format!("{}/api/v1/plugins/publish", environment.api_base_url());
189-
let response = reqwest::Client::new()
189+
let response = http::build_client(Some(&token))?
190190
.post(url)
191-
.header("X-Yaak-Session", token)
192-
.header(reqwest::header::USER_AGENT, user_agent())
193191
.header(reqwest::header::CONTENT_TYPE, "application/zip")
194192
.body(archive)
195193
.send()
@@ -201,7 +199,7 @@ async fn publish(args: PluginPathArg) -> CommandResult {
201199
response.text().await.map_err(|e| format!("Failed reading publish response body: {e}"))?;
202200

203201
if !status.is_success() {
204-
return Err(parse_api_error(status.as_u16(), &body));
202+
return Err(http::parse_api_error(status.as_u16(), &body));
205203
}
206204

207205
let published: PublishResponse = serde_json::from_str(&body)
@@ -389,32 +387,6 @@ fn get_auth_token(environment: Environment) -> CommandResult<Option<String>> {
389387
}
390388
}
391389

392-
fn parse_api_error(status: u16, body: &str) -> String {
393-
if let Ok(value) = serde_json::from_str::<Value>(body) {
394-
if let Some(message) = value.get("message").and_then(Value::as_str) {
395-
return message.to_string();
396-
}
397-
if let Some(error) = value.get("error").and_then(Value::as_str) {
398-
return error.to_string();
399-
}
400-
}
401-
402-
format!("API error {status}: {body}")
403-
}
404-
405-
fn user_agent() -> String {
406-
format!("YaakCli/{} ({})", env!("CARGO_PKG_VERSION"), ua_platform())
407-
}
408-
409-
fn ua_platform() -> &'static str {
410-
match std::env::consts::OS {
411-
"windows" => "Win",
412-
"darwin" => "Mac",
413-
"linux" => "Linux",
414-
_ => "Unknown",
415-
}
416-
}
417-
418390
fn random_name() -> String {
419391
const ADJECTIVES: &[&str] = &[
420392
"young", "youthful", "yellow", "yielding", "yappy", "yawning", "yummy", "yucky", "yearly",

crates-cli/yaak-cli/src/commands/request.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ async fn schema(ctx: &CliContext, request_type: RequestSchemaType) -> CommandRes
8585
.map_err(|e| format!("Failed to serialize WebSocket request schema: {e}"))?,
8686
};
8787

88+
enrich_schema_guidance(&mut schema, request_type);
89+
8890
if let Err(error) = merge_auth_schema_from_plugins(ctx, &mut schema).await {
8991
eprintln!("Warning: Failed to enrich authentication schema from plugins: {error}");
9092
}
@@ -95,6 +97,37 @@ async fn schema(ctx: &CliContext, request_type: RequestSchemaType) -> CommandRes
9597
Ok(())
9698
}
9799

100+
fn enrich_schema_guidance(schema: &mut Value, request_type: RequestSchemaType) {
101+
if !matches!(request_type, RequestSchemaType::Http) {
102+
return;
103+
}
104+
105+
let Some(properties) = schema.get_mut("properties").and_then(Value::as_object_mut) else {
106+
return;
107+
};
108+
109+
if let Some(url_schema) = properties.get_mut("url").and_then(Value::as_object_mut) {
110+
append_description(
111+
url_schema,
112+
"For path segments like `/foo/:id/comments/:commentId`, put concrete values in `urlParameters` using names without `:` (for example `id`, `commentId`).",
113+
);
114+
}
115+
}
116+
117+
fn append_description(schema: &mut Map<String, Value>, extra: &str) {
118+
match schema.get_mut("description") {
119+
Some(Value::String(existing)) if !existing.trim().is_empty() => {
120+
if !existing.ends_with(' ') {
121+
existing.push(' ');
122+
}
123+
existing.push_str(extra);
124+
}
125+
_ => {
126+
schema.insert("description".to_string(), Value::String(extra.to_string()));
127+
}
128+
}
129+
}
130+
98131
async fn merge_auth_schema_from_plugins(
99132
ctx: &CliContext,
100133
schema: &mut Value,

crates-cli/yaak-cli/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod context;
44
mod plugin_events;
55
mod ui;
66
mod utils;
7+
mod version;
78

89
use clap::Parser;
910
use cli::{Cli, Commands, RequestCommands};
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use reqwest::Client;
2+
use reqwest::header::{HeaderMap, HeaderName, HeaderValue, USER_AGENT};
3+
use serde_json::Value;
4+
5+
pub fn build_client(session_token: Option<&str>) -> Result<Client, String> {
6+
let mut headers = HeaderMap::new();
7+
let user_agent = HeaderValue::from_str(&user_agent())
8+
.map_err(|e| format!("Failed to build user-agent header: {e}"))?;
9+
headers.insert(USER_AGENT, user_agent);
10+
11+
if let Some(token) = session_token {
12+
let token_value = HeaderValue::from_str(token)
13+
.map_err(|e| format!("Failed to build session header: {e}"))?;
14+
headers.insert(HeaderName::from_static("x-yaak-session"), token_value);
15+
}
16+
17+
Client::builder()
18+
.default_headers(headers)
19+
.build()
20+
.map_err(|e| format!("Failed to initialize HTTP client: {e}"))
21+
}
22+
23+
pub fn parse_api_error(status: u16, body: &str) -> String {
24+
if let Ok(value) = serde_json::from_str::<Value>(body) {
25+
if let Some(message) = value.get("message").and_then(Value::as_str) {
26+
return message.to_string();
27+
}
28+
if let Some(error) = value.get("error").and_then(Value::as_str) {
29+
return error.to_string();
30+
}
31+
}
32+
33+
format!("API error {status}: {body}")
34+
}
35+
36+
fn user_agent() -> String {
37+
format!("YaakCli/{} ({})", crate::version::cli_version(), ua_platform())
38+
}
39+
40+
fn ua_platform() -> &'static str {
41+
match std::env::consts::OS {
42+
"windows" => "Win",
43+
"darwin" => "Mac",
44+
"linux" => "Linux",
45+
_ => "Unknown",
46+
}
47+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod confirm;
2+
pub mod http;
23
pub mod json;

crates-cli/yaak-cli/src/version.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub fn cli_version() -> &'static str {
2+
option_env!("YAAK_CLI_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"))
3+
}

crates-cli/yaak-cli/tests/request_commands.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,9 @@ fn request_schema_http_outputs_json_schema() {
190190
.assert()
191191
.success()
192192
.stdout(contains("\"type\": \"object\""))
193-
.stdout(contains("\"authentication\""));
193+
.stdout(contains("\"authentication\""))
194+
.stdout(contains("/foo/:id/comments/:commentId"))
195+
.stdout(contains("put concrete values in `urlParameters`"));
194196
}
195197

196198
#[test]

0 commit comments

Comments
 (0)