Skip to content

Commit

Permalink
Merge pull request #11 from shrijan00003/feat/multi-request-support
Browse files Browse the repository at this point in the history
feat/multi-request-support: support individual command with file type
  • Loading branch information
shrijan00003 authored Sep 3, 2024
2 parents 7a90778 + dcc1bd7 commit 379b232
Show file tree
Hide file tree
Showing 12 changed files with 319 additions and 63 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Ignore response file
**/.*.response.txt
**/.*.response.json
**/.*.res.txt
**/.*.response.json
36 changes: 32 additions & 4 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,44 @@ curl -s https://raw.githubusercontent.com/shrijan00003/restler/main/install/linu

## User Guide

1. Create a folder for API collection, default is `restler`
1. Create a folder for the API collection, default is `restler`.
2. Create a folder `requests/<request-name>` like `requests/posts` inside the API collection folder and create a request file inside it.
3. Create a request file with `.request.yaml` extension. For example `posts.request.yaml`
3. Create a request file with `.<http-method>.yaml` extension. For example `posts.post.yaml`, or `posts.get.yaml`
4. (Optional) Create `env` folder inside the API collection folder and create a `.yaml` file inside it. For example `env/default.yaml` for using environment variables in request.
5. (Optional) Create `config.yaml` file inside the API collection folder for configurations like environment.
6. (Optional) Change folder for API collection in runtime by setting `RESTLER_PATH` environment variable. For example `export RESTLER_PATH=app-prefix-collection`
7. Run `restler <request-name>` to run the request. For example `restler posts`
8. Check the output files in `requests/<request-name>` folder. For example `requests/posts/.post.response.txt`
7. Run `restler <http-method> <request-name>` to run the request. For example `restler psot posts` to run post request and `restler get posts` to run get request.
8. Check the output files in `requests/<request-name>` folder. For example `requests/posts/.post.res.txt` for post response and `requests/posts/.get.res.txt` for get request response.
9. for other supports please check the [TODO](./todos/Todo.md) file.

## Commands Available
- `restler post <request-name>`
- `restler get <request-name>`
- `restler patch <request-name>`
- `restler put <request-name>`
- `restler delete <request-name>`

## Proxy Usage

Restler respects the `HTTPS_PROXY` and `HTTP_PROXY` environment variables. You can specify a proxy URL for individual requests using the `R-Proxy-Url` header. To disable the proxy for specific requests, use the `R-Proxy-Enable: N` header.

for eg:
```yaml
Name: Get Posts
URL: "{{API_URL}}"
Method: GET

Headers:
Accept: text/html, application/json
Accept-Encoding: utf-8
R-Proxy-Url: https://something.com:8080
R-Proxy-Enable: N # N or Y, Y is default, if HTTPS_PROXY, HTTP_PROXY or R-Proxy-Url is set
User-Agent: rs-client-0.0.1
Content-Type: application/json

Body:
```
## Inspiration
## Build
Expand Down
200 changes: 153 additions & 47 deletions bin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io"
"log"
"net/http"
"net/url"
"os"
"regexp"
"strings"
Expand Down Expand Up @@ -43,90 +44,158 @@ func (c *Config) Terminate() {
// global environment map
var env map[string]string

// global proxy url
var gProxyUrl string

func main() {
// RESTLER_PATH path, where to run command to create api request
var rsClientPath = os.Getenv("RESTLER_PATH")
if rsClientPath == "" {
var restlerPath = os.Getenv("RESTLER_PATH")
if restlerPath == "" {
fmt.Println("[Restler Log]:RESTLER_PATH is not set, defaulting to restler")
rsClientPath = "restler"
restlerPath = "restler"
}

// Load proxy from env supports both HTTPS_PROXY and HTTP_PROXY
gProxyUrl = os.Getenv("HTTPS_PROXY")
if gProxyUrl == "" {
gProxyUrl = os.Getenv("HTTP_PROXY")
if gProxyUrl == "" {
gProxyUrl = ""
}
}

// Load configs
err := loadWithYaml(fmt.Sprintf("%s/config.yaml", rsClientPath), &config)
err := loadWithYaml(fmt.Sprintf("%s/config.yaml", restlerPath), &config)
if err != nil {
fmt.Println("[Restler Log]:Failed to load config file, taking all defaults, err:", err)
config.DefaultConfig()
}

// Load Environment
err = loadWithYaml(fmt.Sprintf("%s/env/%s.yaml", rsClientPath, config.Env), &env)
err = loadWithYaml(fmt.Sprintf("%s/env/%s.yaml", restlerPath, config.Env), &env)
if err != nil {
fmt.Printf("[Restler Error]: Failed to load environment file! Make sure you have at least default.yaml file in %s/env folder to use environment variables in request!\n", rsClientPath)
fmt.Printf("[Restler Error]: Failed to load environment file! Make sure you have at least default.yaml file in %s/env folder to use environment variables in request!\n", restlerPath)
}

app := &cli.App{
Name: "Restler Application",
Usage: "Developer friendly rest client for developers only!!",
Action: func(cCtx *cli.Context) error {
// consumer will send the request name in the args
// TODO: if not we will run all requests on dir
var requestDir = cCtx.Args().Get(0)
if requestDir == ""{
log.Fatal("[Restler Error]: No request provided! Please provide request name as argument. Request name is the name of the folder in requests folder.")
}
Commands: []*cli.Command{
{
Name: "post",
Aliases: []string{"p"},
Usage: "Run post request",
Action: func(cCtx *cli.Context) error {
return restAction(cCtx, POST, restlerPath)
},
},
{
Name: "get",
Aliases: []string{"g"},
Usage: "Run get request",
Action: func(cCtx *cli.Context) error {
return restAction(cCtx, GET, restlerPath)
},
},
{
Name: "put",
Aliases: []string{"u"},
Usage: "Run put request",
Action: func(cCtx *cli.Context) error {
return restAction(cCtx, PUT, restlerPath)
},
},
{
Name: "delete",
Aliases: []string{"d"},
Usage: "Run delete request",
Action: func(cCtx *cli.Context) error {
return restAction(cCtx, DELETE, restlerPath)
},
},
{
Name: "patch",
Aliases: []string{"m"},
Usage: "Run patch request",
Action: func(cCtx *cli.Context) error {
return restAction(cCtx, PATCH, restlerPath)
},
},
},
}

// request path will have .request.yaml if not we will say that request file not found
var requestDirPath = fmt.Sprintf("%s/requests/%s", rsClientPath, requestDir)
if _, err := os.Stat(requestDirPath); os.IsNotExist(err) {
log.Fatal("[Restler Error]: Request directory not found, please check the path. Request Directory Path: ", requestDirPath)
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}

var requestPath = fmt.Sprintf("%s/%s.request.yaml", requestDirPath, requestDir)
fmt.Println("[Restler Log]: Processing Request: ", requestPath)
}

if _, err := os.Stat(requestPath); os.IsNotExist(err) {
log.Fatal("[Restler Error]: Request file not found, please check the path. Request File Path: ", requestPath)
}
res, err := parseRequest(requestPath)
if err != nil {
log.Fatal("[Restler error]: ",err)
}
body, err := readBody(res)
if err != nil {
log.Fatal("[Restler error]: ",err)
}
type ActionName string

responseBytes, err := prepareResponse(res, body)
if err != nil {
log.Fatal("[Restler error]: ",err)
}
const (
POST ActionName = "post"
GET ActionName = "get"
PUT ActionName = "put"
DELETE ActionName = "delete"
PATCH ActionName = "patch"
OPTIONS ActionName = "options"
HEAD ActionName = "head"
)

// TODO: support different output formats
// If user chooses json format, we should convert the response body to json and write in different file than header.
outputFilePath := fmt.Sprintf("%s/.%s.response.txt", requestDirPath, requestDir)
os.WriteFile(outputFilePath, responseBytes, 0644)
return nil
},
func restAction(cCtx *cli.Context, actionName ActionName, restlerPath string) error {
var req = cCtx.Args().Get(0)
if req == "" {
log.Fatal("[Restler Error]: No request provided! Please provide request name as argument. Request name is the name of the folder in requests folder.")
}

if err := app.Run(os.Args); err != nil {
log.Fatal(err)
var reqPath = fmt.Sprintf("%s/requests/%s", restlerPath, req)
if _, err := os.Stat(reqPath); os.IsNotExist(err) {
log.Fatal("[Restler Error]: Request directory not found, please check the path. Request Directory Path: ", reqPath)
}

var reqFullPath = fmt.Sprintf("%s/%s.%s.yaml", reqPath, req, actionName)
fmt.Println("[Restler Log]: Processing Request: ", reqFullPath)

if _, err := os.Stat(reqFullPath); os.IsNotExist(err) {
log.Fatal("[Restler Error]: Request file not found, please check the path. Request File Path: ", reqFullPath)
}
res, err := parseRequest(reqFullPath)

if err != nil {
log.Fatal("[Restler error]: ", err)
}
body, err := readBody(res)

if err != nil {
log.Fatal("[Restler error]: ", err)
}

responseBytes, err := prepareResponse(res, body)
if err != nil {
log.Fatal("[Restler error]: ", err)
}

outputFilePath := fmt.Sprintf("%s/.%s.res.txt", reqPath, actionName)
os.WriteFile(outputFilePath, responseBytes, 0644)
return nil
}

func prepareResponse(res *http.Response, body []byte) ([]byte, error) {
var buffer bytes.Buffer
buffer.WriteString("-------Status--------\n")
buffer.WriteString(fmt.Sprintf("Status Code: %d, Status: %s\n", res.StatusCode, res.Status))
buffer.WriteString("\n\n")
buffer.WriteString("-------Header--------\n")

for key, value := range res.Header {
buffer.WriteString(fmt.Sprintf("%s: %s\n", key, value))
}
buffer.WriteString("\n")

buffer.WriteString("\n\n")
buffer.WriteString("-------Body--------\n")
buffer.Write(body)

buffer.WriteString("\n\n")
buffer.WriteString("-------Request--------\n")
buffer.WriteString(fmt.Sprintf("Method: %s, URL: %s\n", res.Request.Method, res.Request.URL))
return buffer.Bytes(), nil
}

Expand Down Expand Up @@ -254,7 +323,44 @@ func validateRequest(r *Request) error {
}

func processRequest(req *Request) (*http.Response, error) {
client := &http.Client{}
var proxyURL *url.URL = nil
var transport *http.Transport = nil
var client *http.Client = nil

sProxyEnable := req.Headers["R-Proxy-Enable"]
if sProxyEnable == "" {
sProxyEnable = "Y"
}

if sProxyEnable == "N" {
client = &http.Client{}
} else {
sProxyUrl := req.Headers["R-Proxy-Url"]

if sProxyUrl == "" {
sProxyUrl = gProxyUrl
}

if sProxyUrl != "" {
var err error
proxyURL, err = url.Parse(sProxyUrl)
if err != nil {
return nil, fmt.Errorf("error parsing proxy url, error: %s", err)
}
}

if proxyURL != nil {
transport = &http.Transport{
Proxy: http.ProxyURL(proxyURL),
}
client = &http.Client{
Transport: transport,
}
} else {
client = &http.Client{}
}
}

var _bytes []byte
var err error
if req.Body != nil {
Expand Down
4 changes: 2 additions & 2 deletions install/darwin-amd64.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Install for MacOS x86_64 architecture
# Variables
REPO="shrijan00003/restler"
VERSION="v0.0.1-dev.2"
VERSION="v0.0.1-dev.3"
BINARY_NAME="restler"
TAR_FILE="${BINARY_NAME}-${VERSION}-darwin-amd64.tar.gz"
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${VERSION}/${TAR_FILE}"
Expand All @@ -25,4 +25,4 @@ echo "Cleaning up..."
rm /tmp/${TAR_FILE}
rm /tmp/${BINARY_NAME}

echo "Installation completed. You can now run '${BINARY_NAME}' from anywhere."
echo "Installation completed. You can now run '${BINARY_NAME}' from anywhere."
4 changes: 2 additions & 2 deletions install/darwin-arm64.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Variables
REPO="shrijan00003/restler"
VERSION="v0.0.1-dev.2"
VERSION="v0.0.1-dev.3"
BINARY_NAME="restler"
TAR_FILE="${BINARY_NAME}-${VERSION}-darwin-arm64.tar.gz"
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${VERSION}/${TAR_FILE}"
Expand All @@ -24,4 +24,4 @@ echo "Cleaning up..."
rm /tmp/${TAR_FILE}
rm /tmp/${BINARY_NAME}

echo "Installation completed. You can now run '${BINARY_NAME}' from anywhere."
echo "Installation completed. You can now run '${BINARY_NAME}' from anywhere."
4 changes: 2 additions & 2 deletions install/linux-amd64.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Variables
REPO="shrijan00003/restler"
VERSION="v0.0.1-dev.2"
VERSION="v0.0.1-dev.3"
BINARY_NAME="restler"
TAR_FILE="${BINARY_NAME}-${VERSION}-linux-amd64.tar.gz"
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${VERSION}/${TAR_FILE}"
Expand All @@ -24,4 +24,4 @@ echo "Cleaning up..."
rm /tmp/${TAR_FILE}
rm /tmp/${BINARY_NAME}

echo "Installation completed. You can now run '${BINARY_NAME}' from anywhere."
echo "Installation completed. You can now run '${BINARY_NAME}' from anywhere."
13 changes: 13 additions & 0 deletions restler/requests/posts/posts.get.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Name: Get Posts
URL: "{{API_URL}}"
Method: GET

Headers:
Accept: text/html, application/json
Accept-Encoding: utf-8
R-Proxy-Url: https://something.com:8080
R-Proxy-Enable: N # N or Y, Y is default, if HTTPS_PROXY, HTTP_PROXY or R-Proxy-Url is set
User-Agent: rs-client-0.0.1
Content-Type: application/json

Body:
Loading

0 comments on commit 379b232

Please sign in to comment.