443 lines
11 KiB
Bash
Executable file
443 lines
11 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Script Name: api
|
|
# Description: API testing helper with saved requests and response management
|
|
# Usage: api save login "POST https://api.com/login" -d '{"user":"test"}'
|
|
# api run login
|
|
# api list
|
|
# api history login
|
|
|
|
VERSION="1.0.0"
|
|
API_DIR="$HOME/.api"
|
|
REQUESTS_DIR="$API_DIR/requests"
|
|
RESPONSES_DIR="$API_DIR/responses"
|
|
TOKENS_FILE="$API_DIR/tokens.json"
|
|
|
|
# Colors
|
|
readonly GREEN='\033[0;32m'
|
|
readonly YELLOW='\033[1;33m'
|
|
readonly BLUE='\033[0;34m'
|
|
readonly RED='\033[0;31m'
|
|
readonly CYAN='\033[0;36m'
|
|
readonly BOLD='\033[1m'
|
|
readonly NC='\033[0m'
|
|
|
|
# Initialize API directory structure
|
|
init_api() {
|
|
mkdir -p "$REQUESTS_DIR" "$RESPONSES_DIR"
|
|
if [[ ! -f "$TOKENS_FILE" ]]; then
|
|
echo '{}' > "$TOKENS_FILE"
|
|
fi
|
|
}
|
|
|
|
show_help() {
|
|
echo -e "${BOLD}api${NC} - API Testing Helper v${VERSION}"
|
|
echo
|
|
echo -e "${BOLD}USAGE:${NC}"
|
|
echo " api <command> [args]"
|
|
echo
|
|
echo -e "${BOLD}COMMANDS:${NC}"
|
|
echo -e " ${CYAN}save NAME CURL_ARGS${NC} Save HTTP request"
|
|
echo -e " ${CYAN}run NAME [VARS]${NC} Run saved request"
|
|
echo -e " ${CYAN}list${NC} List saved requests"
|
|
echo -e " ${CYAN}show NAME${NC} Show request details"
|
|
echo -e " ${CYAN}delete NAME${NC} Delete saved request"
|
|
echo -e " ${CYAN}history NAME${NC} Show response history"
|
|
echo -e " ${CYAN}diff NAME${NC} Diff last two responses"
|
|
echo -e " ${CYAN}token set KEY VAL${NC} Save auth token"
|
|
echo -e " ${CYAN}token get KEY${NC} Get auth token"
|
|
echo -e " ${CYAN}export NAME curl${NC} Export as curl command"
|
|
echo
|
|
echo -e "${BOLD}EXAMPLES:${NC}"
|
|
echo " # Save a login request"
|
|
echo " api save login \"POST https://api.example.com/login\" \\"
|
|
echo " -H \"Content-Type: application/json\" \\"
|
|
echo " -d '{\"user\":\"test\",\"pass\":\"\${PASSWORD}\"}'"
|
|
echo
|
|
echo " # Run with variable substitution"
|
|
echo " api run login PASSWORD=secret123"
|
|
echo
|
|
echo " # Save auth token from response"
|
|
echo " api token set AUTH_TOKEN \"Bearer abc123\""
|
|
echo
|
|
echo " # Use token in request"
|
|
echo " api save profile \"GET https://api.example.com/profile\" \\"
|
|
echo " -H \"Authorization: \${AUTH_TOKEN}\""
|
|
echo
|
|
echo -e "${BOLD}FEATURES:${NC}"
|
|
echo " - Variable substitution (\${VAR})"
|
|
echo " - Response history"
|
|
echo " - Pretty-print JSON"
|
|
echo " - Diff responses"
|
|
echo " - Token management"
|
|
echo
|
|
echo -e "${BOLD}NOTES:${NC}"
|
|
echo " Requests: $REQUESTS_DIR"
|
|
echo " Responses: $RESPONSES_DIR"
|
|
echo " Tokens: $TOKENS_FILE"
|
|
}
|
|
|
|
# Save request
|
|
save_request() {
|
|
local name=$1
|
|
shift
|
|
local request_file="$REQUESTS_DIR/$name.sh"
|
|
|
|
# Save the curl command
|
|
cat > "$request_file" << EOF
|
|
#!/usr/bin/env bash
|
|
# API Request: $name
|
|
# Saved: $(date)
|
|
|
|
curl -w "\\n\\nStatus: %{http_code}\\nTime: %{time_total}s\\n" \\
|
|
$@
|
|
EOF
|
|
|
|
chmod +x "$request_file"
|
|
echo -e "${GREEN}✓${NC} Saved request: $name"
|
|
echo -e "${CYAN}File:${NC} $request_file"
|
|
}
|
|
|
|
# Run saved request
|
|
run_request() {
|
|
local name=$1
|
|
shift
|
|
local request_file="$REQUESTS_DIR/$name.sh"
|
|
|
|
if [[ ! -f "$request_file" ]]; then
|
|
echo -e "${RED}Error:${NC} Request not found: $name" >&2
|
|
echo "Use 'api list' to see available requests" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Parse variables (KEY=VALUE format)
|
|
declare -A vars
|
|
for arg in "$@"; do
|
|
if [[ "$arg" =~ ^([A-Z_]+)=(.+)$ ]]; then
|
|
vars[${BASH_REMATCH[1]}]="${BASH_REMATCH[2]}"
|
|
fi
|
|
done
|
|
|
|
# Read request, substitute variables
|
|
request_content=$(cat "$request_file")
|
|
for var in "${!vars[@]}"; do
|
|
request_content="${request_content//\$\{$var\}/${vars[$var]}}"
|
|
done
|
|
|
|
# Also substitute from tokens file
|
|
if command -v jq &>/dev/null && [[ -f "$TOKENS_FILE" ]]; then
|
|
while IFS= read -r line; do
|
|
key=$(echo "$line" | jq -r '.key')
|
|
val=$(echo "$line" | jq -r '.value')
|
|
request_content="${request_content//\$\{$key\}/$val}"
|
|
done < <(jq -c 'to_entries[]' "$TOKENS_FILE")
|
|
fi
|
|
|
|
# Save response with timestamp
|
|
timestamp=$(date '+%Y%m%d-%H%M%S')
|
|
response_file="$RESPONSES_DIR/${name}_${timestamp}.txt"
|
|
|
|
echo -e "${BOLD}${CYAN}Running: $name${NC}"
|
|
echo
|
|
|
|
# Execute and save response
|
|
echo "$request_content" | bash | tee "$response_file"
|
|
|
|
echo
|
|
echo -e "${GREEN}✓${NC} Response saved: $response_file"
|
|
|
|
# Pretty-print JSON if possible
|
|
if command -v jq &>/dev/null; then
|
|
if head -1 "$response_file" | jq empty 2>/dev/null; then
|
|
echo
|
|
echo -e "${BOLD}${CYAN}JSON Response:${NC}"
|
|
head -n -3 "$response_file" | jq .
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# List saved requests
|
|
list_requests() {
|
|
if [[ ! -d "$REQUESTS_DIR" ]] || [[ -z "$(ls -A "$REQUESTS_DIR" 2>/dev/null)" ]]; then
|
|
echo -e "${YELLOW}No saved requests${NC}"
|
|
exit 0
|
|
fi
|
|
|
|
echo -e "${BOLD}${CYAN}Saved Requests:${NC}"
|
|
echo
|
|
|
|
for file in "$REQUESTS_DIR"/*.sh; do
|
|
name=$(basename "$file" .sh)
|
|
method=$(grep -oP 'POST|GET|PUT|DELETE|PATCH' "$file" | head -1 || echo "?")
|
|
url=$(grep -oP 'https?://[^\s"]+' "$file" | head -1 || echo "?")
|
|
|
|
printf " %-20s ${CYAN}%-7s${NC} %s\n" "$name" "$method" "$url"
|
|
done
|
|
}
|
|
|
|
# Show request details
|
|
show_request() {
|
|
local name=$1
|
|
local request_file="$REQUESTS_DIR/$name.sh"
|
|
|
|
if [[ ! -f "$request_file" ]]; then
|
|
echo -e "${RED}Error:${NC} Request not found: $name" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${BOLD}${CYAN}Request: $name${NC}"
|
|
echo
|
|
|
|
# Use bat if available for syntax highlighting
|
|
if command -v bat &>/dev/null; then
|
|
bat "$request_file"
|
|
else
|
|
cat "$request_file"
|
|
fi
|
|
}
|
|
|
|
# Delete request
|
|
delete_request() {
|
|
local name=$1
|
|
local request_file="$REQUESTS_DIR/$name.sh"
|
|
|
|
if [[ ! -f "$request_file" ]]; then
|
|
echo -e "${RED}Error:${NC} Request not found: $name" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo -n "Delete request '$name'? (y/N) "
|
|
read -r response
|
|
if [[ "$response" =~ ^[Yy]$ ]]; then
|
|
rm "$request_file"
|
|
echo -e "${GREEN}✓${NC} Deleted: $name"
|
|
else
|
|
echo "Cancelled"
|
|
fi
|
|
}
|
|
|
|
# Show response history
|
|
show_history() {
|
|
local name=$1
|
|
|
|
responses=$(find "$RESPONSES_DIR" -name "${name}_*.txt" 2>/dev/null | sort -r)
|
|
|
|
if [[ -z "$responses" ]]; then
|
|
echo -e "${YELLOW}No response history for: $name${NC}"
|
|
exit 0
|
|
fi
|
|
|
|
echo -e "${BOLD}${CYAN}Response History: $name${NC}"
|
|
echo
|
|
|
|
echo "$responses" | while read -r file; do
|
|
timestamp=$(basename "$file" | sed "s/${name}_//" | sed 's/.txt//')
|
|
status=$(tail -3 "$file" | grep "Status:" | awk '{print $2}')
|
|
time=$(tail -3 "$file" | grep "Time:" | awk '{print $2}')
|
|
|
|
# Color status
|
|
if [[ "$status" =~ ^2 ]]; then
|
|
status_colored="${GREEN}$status${NC}"
|
|
elif [[ "$status" =~ ^4 ]]; then
|
|
status_colored="${YELLOW}$status${NC}"
|
|
elif [[ "$status" =~ ^5 ]]; then
|
|
status_colored="${RED}$status${NC}"
|
|
else
|
|
status_colored="$status"
|
|
fi
|
|
|
|
echo -e " $timestamp - Status: $status_colored - Time: $time"
|
|
done
|
|
|
|
echo
|
|
echo -e "${CYAN}Tip:${NC} Use 'api diff $name' to compare last two responses"
|
|
}
|
|
|
|
# Diff last two responses
|
|
diff_responses() {
|
|
local name=$1
|
|
|
|
responses=$(find "$RESPONSES_DIR" -name "${name}_*.txt" 2>/dev/null | sort -r | head -2)
|
|
count=$(echo "$responses" | wc -l)
|
|
|
|
if [[ $count -lt 2 ]]; then
|
|
echo -e "${YELLOW}Need at least 2 responses to diff${NC}"
|
|
exit 0
|
|
fi
|
|
|
|
file1=$(echo "$responses" | sed -n 1p)
|
|
file2=$(echo "$responses" | sed -n 2p)
|
|
|
|
echo -e "${BOLD}${CYAN}Diff: $name${NC}"
|
|
echo -e "${CYAN}Latest:${NC} $(basename "$file1")"
|
|
echo -e "${CYAN}Previous:${NC} $(basename "$file2")"
|
|
echo
|
|
|
|
# Remove status/time lines before diff
|
|
diff -u <(head -n -3 "$file2") <(head -n -3 "$file1") || true
|
|
}
|
|
|
|
# Token management
|
|
manage_token() {
|
|
local action=$1
|
|
shift
|
|
|
|
case "$action" in
|
|
set)
|
|
if [[ $# -lt 2 ]]; then
|
|
echo -e "${RED}Error:${NC} Usage: api token set KEY VALUE" >&2
|
|
exit 1
|
|
fi
|
|
key=$1
|
|
value=$2
|
|
|
|
# Update JSON file
|
|
if command -v jq &>/dev/null; then
|
|
jq --arg k "$key" --arg v "$value" '. + {($k): $v}' "$TOKENS_FILE" > "$TOKENS_FILE.tmp"
|
|
mv "$TOKENS_FILE.tmp" "$TOKENS_FILE"
|
|
echo -e "${GREEN}✓${NC} Token saved: $key"
|
|
else
|
|
echo -e "${RED}Error:${NC} jq required for token management" >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
|
|
get)
|
|
if [[ $# -lt 1 ]]; then
|
|
echo -e "${RED}Error:${NC} Usage: api token get KEY" >&2
|
|
exit 1
|
|
fi
|
|
key=$1
|
|
|
|
if command -v jq &>/dev/null; then
|
|
value=$(jq -r ".$key // empty" "$TOKENS_FILE")
|
|
if [[ -n "$value" ]]; then
|
|
echo "$value"
|
|
else
|
|
echo -e "${YELLOW}Token not found: $key${NC}" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
;;
|
|
|
|
list)
|
|
if command -v jq &>/dev/null; then
|
|
echo -e "${BOLD}${CYAN}Saved Tokens:${NC}"
|
|
jq -r 'keys[]' "$TOKENS_FILE"
|
|
fi
|
|
;;
|
|
|
|
*)
|
|
echo -e "${RED}Error:${NC} Unknown token action: $action" >&2
|
|
echo "Use: set, get, list" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Export request
|
|
export_request() {
|
|
local name=$1
|
|
local format=${2:-curl}
|
|
local request_file="$REQUESTS_DIR/$name.sh"
|
|
|
|
if [[ ! -f "$request_file" ]]; then
|
|
echo -e "${RED}Error:${NC} Request not found: $name" >&2
|
|
exit 1
|
|
fi
|
|
|
|
case "$format" in
|
|
curl)
|
|
# Extract the curl command
|
|
grep -A 999 'curl' "$request_file" | grep -v '^#'
|
|
;;
|
|
*)
|
|
echo -e "${RED}Error:${NC} Unknown format: $format" >&2
|
|
echo "Supported: curl" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Initialize
|
|
init_api
|
|
|
|
# Parse command
|
|
if [[ $# -eq 0 ]]; then
|
|
show_help
|
|
exit 0
|
|
fi
|
|
|
|
case $1 in
|
|
-h|--help|help)
|
|
show_help
|
|
;;
|
|
save)
|
|
if [[ $# -lt 3 ]]; then
|
|
echo -e "${RED}Error:${NC} Usage: api save NAME CURL_ARGS" >&2
|
|
exit 1
|
|
fi
|
|
shift
|
|
save_request "$@"
|
|
;;
|
|
run)
|
|
if [[ $# -lt 2 ]]; then
|
|
echo -e "${RED}Error:${NC} Usage: api run NAME [VARS]" >&2
|
|
exit 1
|
|
fi
|
|
shift
|
|
run_request "$@"
|
|
;;
|
|
list|ls)
|
|
list_requests
|
|
;;
|
|
show)
|
|
if [[ $# -lt 2 ]]; then
|
|
echo -e "${RED}Error:${NC} Usage: api show NAME" >&2
|
|
exit 1
|
|
fi
|
|
show_request "$2"
|
|
;;
|
|
delete|rm)
|
|
if [[ $# -lt 2 ]]; then
|
|
echo -e "${RED}Error:${NC} Usage: api delete NAME" >&2
|
|
exit 1
|
|
fi
|
|
delete_request "$2"
|
|
;;
|
|
history)
|
|
if [[ $# -lt 2 ]]; then
|
|
echo -e "${RED}Error:${NC} Usage: api history NAME" >&2
|
|
exit 1
|
|
fi
|
|
show_history "$2"
|
|
;;
|
|
diff)
|
|
if [[ $# -lt 2 ]]; then
|
|
echo -e "${RED}Error:${NC} Usage: api diff NAME" >&2
|
|
exit 1
|
|
fi
|
|
diff_responses "$2"
|
|
;;
|
|
token)
|
|
if [[ $# -lt 2 ]]; then
|
|
echo -e "${RED}Error:${NC} Usage: api token <set|get|list> ..." >&2
|
|
exit 1
|
|
fi
|
|
shift
|
|
manage_token "$@"
|
|
;;
|
|
export)
|
|
if [[ $# -lt 2 ]]; then
|
|
echo -e "${RED}Error:${NC} Usage: api export NAME [FORMAT]" >&2
|
|
exit 1
|
|
fi
|
|
export_request "$2" "${3:-curl}"
|
|
;;
|
|
*)
|
|
echo -e "${RED}Error:${NC} Unknown command: $1" >&2
|
|
echo "Run 'api --help' for usage" >&2
|
|
exit 1
|
|
;;
|
|
esac
|