diff --git a/README.md b/README.md index 3bb04fcc0..b0ef42eb4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# HTTP Snippet [![version][npm-version]][npm-url] [![License][npm-license]][license-url] +# HTTPSnippet -> HTTP Request snippet generator for _many_ languages & tools including: `cURL`, `HTTPie`, `Javascript`, `Node`, `C`, `Java`, `PHP`, `Objective-C`, `Swift`, `Python`, `Ruby`, `C#`, `Go`, `OCaml` and [more](https://github.com/Kong/httpsnippet/wiki/Targets)! +> HTTP Request snippet generator for _many_ languages & tools including: `cURL`, `HTTPie`, `Javascript`, `Node`, `C`, `Java`, `PHP`, `Objective-C`, `Swift`, `Python`, `Ruby`, `Rust`, `C#`, `Go`, `OCaml` and [more](https://github.com/Kong/httpsnippet/wiki/Targets)! Relies on the popular [HAR](http://www.softwareishard.com/blog/har-12-spec/#request) format to import data and describe HTTP calls. diff --git a/package.json b/package.json index 718132f3f..d74607fb3 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "keywords": [ "api", "clojure", + "crystal", "csharp", "curl", "go", @@ -23,6 +24,7 @@ "python", "requests", "ruby", + "rust", "shell", "snippet", "swift", diff --git a/src/helpers/__snapshots__/utils.test.ts.snap b/src/helpers/__snapshots__/utils.test.ts.snap index cc6b060bc..0f7fe5a72 100644 --- a/src/helpers/__snapshots__/utils.test.ts.snap +++ b/src/helpers/__snapshots__/utils.test.ts.snap @@ -31,6 +31,20 @@ exports[`availableTargets > returns all available targets 1`] = ` "key": "clojure", "title": "Clojure", }, + { + "clients": [ + { + "description": "Crystal HTTP client", + "extname": ".cr", + "key": "native", + "link": "https://crystal-lang.org/api/master/HTTP/Client.html", + "title": "http::client", + }, + ], + "default": "native", + "key": "crystal", + "title": "Crystal", + }, { "cli": "dotnet", "clients": [ @@ -338,11 +352,32 @@ exports[`availableTargets > returns all available targets 1`] = ` "link": "http://ruby-doc.org/stdlib-2.2.1/libdoc/net/http/rdoc/Net/HTTP.html", "title": "net::http", }, + { + "description": "Faraday HTTP client", + "extname": ".rb", + "key": "faraday", + "link": "https://github.com/lostisland/faraday", + "title": "faraday", + }, ], "default": "native", "key": "ruby", "title": "Ruby", }, + { + "clients": [ + { + "description": "reqwest HTTP library", + "extname": ".rs", + "key": "reqwest", + "link": "https://docs.rs/reqwest/latest/reqwest/", + "title": "reqwest", + }, + ], + "default": "reqwest", + "key": "rust", + "title": "Rust", + }, { "cli": "%s", "clients": [ diff --git a/src/helpers/code-builder.ts b/src/helpers/code-builder.ts index 80fbf4245..d8176e4d1 100644 --- a/src/helpers/code-builder.ts +++ b/src/helpers/code-builder.ts @@ -59,6 +59,18 @@ export class CodeBuilder { this.code.push(newLine); }; + /** + * Add the line to the end of the last line. Creates a new line + * if no lines exist yet. + */ + pushToLast = (line: string): void => { + if (!this.code) { + this.push(line); + } + const updatedLine = `${this.code[this.code.length - 1]}${line}`; + this.code[this.code.length - 1] = updatedLine; + }; + /** * Add an empty line at the end of current lines */ diff --git a/src/targets/crystal/native/client.test.ts b/src/targets/crystal/native/client.test.ts new file mode 100644 index 000000000..bd7157a63 --- /dev/null +++ b/src/targets/crystal/native/client.test.ts @@ -0,0 +1,19 @@ +import type { Request } from '../../../index.js'; + +import httpInsecure from '../../../fixtures/requests/http-insecure.cjs'; +import { runCustomFixtures } from '../../../fixtures/runCustomFixtures'; + +runCustomFixtures({ + targetId: 'crystal', + clientId: 'native', + tests: [ + { + it: 'should support the insecureSkipVerify option', + input: httpInsecure.log.entries[0].request as Request, + options: { + insecureSkipVerify: true, + }, + expected: 'insecure-skip-verify.cr', + }, + ], +}); diff --git a/src/targets/crystal/native/client.ts b/src/targets/crystal/native/client.ts new file mode 100644 index 000000000..6335280be --- /dev/null +++ b/src/targets/crystal/native/client.ts @@ -0,0 +1,70 @@ +/** + * @description + * HTTP code snippet generator for native Crystal + * + * @author + * @18183883296 + * + * for any questions or issues regarding the generated code snippet, please open an issue mentioning the author. + */ +import type { Client } from '../../index.js'; + +import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForDoubleQuotes } from '../../../helpers/escape'; + +export interface CrystalNativeOptions { + insecureSkipVerify?: boolean; +} + +export const native: Client = { + info: { + key: 'native', + title: 'http::client', + link: 'https://crystal-lang.org/api/master/HTTP/Client.html', + description: 'Crystal HTTP client', + extname: '.cr', + }, + convert: ({ method: rawMethod, fullUrl, postData, allHeaders }, options = {}) => { + const { insecureSkipVerify = false } = options; + + const { push, blank, join } = new CodeBuilder(); + + push('require "http/client"'); + + blank(); + + push(`url = "${fullUrl}"`); + + const headers = Object.keys(allHeaders); + if (headers.length) { + push('headers = HTTP::Headers{'); + headers.forEach(key => { + push(` "${key}" => "${escapeForDoubleQuotes(allHeaders[key])}"`); + }); + push('}'); + } + + if (postData.text) { + push(`reqBody = ${JSON.stringify(postData.text)}`); + } + + blank(); + + const method = rawMethod.toUpperCase(); + const methods = ['GET', 'POST', 'HEAD', 'DELETE', 'PATCH', 'PUT', 'OPTIONS']; + + const headersContext = headers.length ? ', headers: headers' : ''; + const bodyContext = postData.text ? ', body: reqBody' : ''; + const sslContext = insecureSkipVerify ? ', tls: OpenSSL::SSL::Context::Client.insecure' : ''; + + if (methods.includes(method)) { + push(`response = HTTP::Client.${method.toLowerCase()} url${headersContext}${bodyContext}${sslContext}`); + } else { + push(`response = HTTP::Client.exec "${method}", url${headersContext}${bodyContext}${sslContext}`); + } + + push('puts response.body'); + + return join(); + }, +}; diff --git a/src/targets/crystal/native/fixtures/application-form-encoded.cr b/src/targets/crystal/native/fixtures/application-form-encoded.cr new file mode 100644 index 000000000..b375abb2f --- /dev/null +++ b/src/targets/crystal/native/fixtures/application-form-encoded.cr @@ -0,0 +1,10 @@ +require "http/client" + +url = "https://httpbin.org/anything" +headers = HTTP::Headers{ + "content-type" => "application/x-www-form-urlencoded" +} +reqBody = "foo=bar&hello=world" + +response = HTTP::Client.post url, headers: headers, body: reqBody +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/application-json.cr b/src/targets/crystal/native/fixtures/application-json.cr new file mode 100644 index 000000000..7b856c357 --- /dev/null +++ b/src/targets/crystal/native/fixtures/application-json.cr @@ -0,0 +1,10 @@ +require "http/client" + +url = "https://httpbin.org/anything" +headers = HTTP::Headers{ + "content-type" => "application/json" +} +reqBody = "{\"number\":1,\"string\":\"f\\\"oo\",\"arr\":[1,2,3],\"nested\":{\"a\":\"b\"},\"arr_mix\":[1,\"a\",{\"arr_mix_nested\":[]}],\"boolean\":false}" + +response = HTTP::Client.post url, headers: headers, body: reqBody +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/cookies.cr b/src/targets/crystal/native/fixtures/cookies.cr new file mode 100644 index 000000000..65b1843e0 --- /dev/null +++ b/src/targets/crystal/native/fixtures/cookies.cr @@ -0,0 +1,9 @@ +require "http/client" + +url = "https://httpbin.org/cookies" +headers = HTTP::Headers{ + "cookie" => "foo=bar; bar=baz" +} + +response = HTTP::Client.get url, headers: headers +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/custom-method.cr b/src/targets/crystal/native/fixtures/custom-method.cr new file mode 100644 index 000000000..be0f08193 --- /dev/null +++ b/src/targets/crystal/native/fixtures/custom-method.cr @@ -0,0 +1,6 @@ +require "http/client" + +url = "https://httpbin.org/anything" + +response = HTTP::Client.exec "PROPFIND", url +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/full.cr b/src/targets/crystal/native/fixtures/full.cr new file mode 100644 index 000000000..c7d2edbac --- /dev/null +++ b/src/targets/crystal/native/fixtures/full.cr @@ -0,0 +1,12 @@ +require "http/client" + +url = "https://httpbin.org/anything?foo=bar&foo=baz&baz=abc&key=value" +headers = HTTP::Headers{ + "cookie" => "foo=bar; bar=baz" + "accept" => "application/json" + "content-type" => "application/x-www-form-urlencoded" +} +reqBody = "foo=bar" + +response = HTTP::Client.post url, headers: headers, body: reqBody +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/headers.cr b/src/targets/crystal/native/fixtures/headers.cr new file mode 100644 index 000000000..f1e31f971 --- /dev/null +++ b/src/targets/crystal/native/fixtures/headers.cr @@ -0,0 +1,12 @@ +require "http/client" + +url = "https://httpbin.org/headers" +headers = HTTP::Headers{ + "accept" => "application/json" + "x-foo" => "Bar" + "x-bar" => "Foo" + "quoted-value" => "\"quoted\" 'string'" +} + +response = HTTP::Client.get url, headers: headers +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/http-insecure.cr b/src/targets/crystal/native/fixtures/http-insecure.cr new file mode 100644 index 000000000..40e8ca4dc --- /dev/null +++ b/src/targets/crystal/native/fixtures/http-insecure.cr @@ -0,0 +1,6 @@ +require "http/client" + +url = "http://httpbin.org/anything" + +response = HTTP::Client.get url +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/insecure-skip-verify.cr b/src/targets/crystal/native/fixtures/insecure-skip-verify.cr new file mode 100644 index 000000000..04161e83d --- /dev/null +++ b/src/targets/crystal/native/fixtures/insecure-skip-verify.cr @@ -0,0 +1,6 @@ +require "http/client" + +url = "http://httpbin.org/anything" + +response = HTTP::Client.get url, tls: OpenSSL::SSL::Context::Client.insecure +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/jsonObj-multiline.cr b/src/targets/crystal/native/fixtures/jsonObj-multiline.cr new file mode 100644 index 000000000..c9a305b34 --- /dev/null +++ b/src/targets/crystal/native/fixtures/jsonObj-multiline.cr @@ -0,0 +1,10 @@ +require "http/client" + +url = "https://httpbin.org/anything" +headers = HTTP::Headers{ + "content-type" => "application/json" +} +reqBody = "{\n \"foo\": \"bar\"\n}" + +response = HTTP::Client.post url, headers: headers, body: reqBody +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/jsonObj-null-value.cr b/src/targets/crystal/native/fixtures/jsonObj-null-value.cr new file mode 100644 index 000000000..929294252 --- /dev/null +++ b/src/targets/crystal/native/fixtures/jsonObj-null-value.cr @@ -0,0 +1,10 @@ +require "http/client" + +url = "https://httpbin.org/anything" +headers = HTTP::Headers{ + "content-type" => "application/json" +} +reqBody = "{\"foo\":null}" + +response = HTTP::Client.post url, headers: headers, body: reqBody +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/multipart-data.cr b/src/targets/crystal/native/fixtures/multipart-data.cr new file mode 100644 index 000000000..32950c3e9 --- /dev/null +++ b/src/targets/crystal/native/fixtures/multipart-data.cr @@ -0,0 +1,10 @@ +require "http/client" + +url = "https://httpbin.org/anything" +headers = HTTP::Headers{ + "content-type" => "multipart/form-data; boundary=---011000010111000001101001" +} +reqBody = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"src/fixtures/files/hello.txt\"\r\nContent-Type: text/plain\r\n\r\nHello World\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bar\"\r\n\r\nBonjour le monde\r\n-----011000010111000001101001--" + +response = HTTP::Client.post url, headers: headers, body: reqBody +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/multipart-file.cr b/src/targets/crystal/native/fixtures/multipart-file.cr new file mode 100644 index 000000000..ce98836ee --- /dev/null +++ b/src/targets/crystal/native/fixtures/multipart-file.cr @@ -0,0 +1,10 @@ +require "http/client" + +url = "https://httpbin.org/anything" +headers = HTTP::Headers{ + "content-type" => "multipart/form-data; boundary=---011000010111000001101001" +} +reqBody = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"src/fixtures/files/hello.txt\"\r\nContent-Type: text/plain\r\n\r\n\r\n-----011000010111000001101001--" + +response = HTTP::Client.post url, headers: headers, body: reqBody +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/multipart-form-data-no-params.cr b/src/targets/crystal/native/fixtures/multipart-form-data-no-params.cr new file mode 100644 index 000000000..8be9a6889 --- /dev/null +++ b/src/targets/crystal/native/fixtures/multipart-form-data-no-params.cr @@ -0,0 +1,9 @@ +require "http/client" + +url = "https://httpbin.org/anything" +headers = HTTP::Headers{ + "Content-Type" => "multipart/form-data" +} + +response = HTTP::Client.post url, headers: headers +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/multipart-form-data.cr b/src/targets/crystal/native/fixtures/multipart-form-data.cr new file mode 100644 index 000000000..6d7aa5a00 --- /dev/null +++ b/src/targets/crystal/native/fixtures/multipart-form-data.cr @@ -0,0 +1,10 @@ +require "http/client" + +url = "https://httpbin.org/anything" +headers = HTTP::Headers{ + "Content-Type" => "multipart/form-data; boundary=---011000010111000001101001" +} +reqBody = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\nbar\r\n-----011000010111000001101001--" + +response = HTTP::Client.post url, headers: headers, body: reqBody +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/nested.cr b/src/targets/crystal/native/fixtures/nested.cr new file mode 100644 index 000000000..18581304b --- /dev/null +++ b/src/targets/crystal/native/fixtures/nested.cr @@ -0,0 +1,6 @@ +require "http/client" + +url = "https://httpbin.org/anything?foo%5Bbar%5D=baz%2Czap&fiz=buz&key=value" + +response = HTTP::Client.get url +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/postdata-malformed.cr b/src/targets/crystal/native/fixtures/postdata-malformed.cr new file mode 100644 index 000000000..26d7ef98b --- /dev/null +++ b/src/targets/crystal/native/fixtures/postdata-malformed.cr @@ -0,0 +1,9 @@ +require "http/client" + +url = "https://httpbin.org/anything" +headers = HTTP::Headers{ + "content-type" => "application/json" +} + +response = HTTP::Client.post url, headers: headers +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/query-encoded.cr b/src/targets/crystal/native/fixtures/query-encoded.cr new file mode 100644 index 000000000..1e525e99c --- /dev/null +++ b/src/targets/crystal/native/fixtures/query-encoded.cr @@ -0,0 +1,6 @@ +require "http/client" + +url = "https://httpbin.org/anything?startTime=2019-06-13T19%3A08%3A25.455Z&endTime=2015-09-15T14%3A00%3A12-04%3A00" + +response = HTTP::Client.get url +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/query.cr b/src/targets/crystal/native/fixtures/query.cr new file mode 100644 index 000000000..320b6dcce --- /dev/null +++ b/src/targets/crystal/native/fixtures/query.cr @@ -0,0 +1,6 @@ +require "http/client" + +url = "https://httpbin.org/anything?foo=bar&foo=baz&baz=abc&key=value" + +response = HTTP::Client.get url +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/short.cr b/src/targets/crystal/native/fixtures/short.cr new file mode 100644 index 000000000..c3a01fc5f --- /dev/null +++ b/src/targets/crystal/native/fixtures/short.cr @@ -0,0 +1,6 @@ +require "http/client" + +url = "https://httpbin.org/anything" + +response = HTTP::Client.get url +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/native/fixtures/text-plain.cr b/src/targets/crystal/native/fixtures/text-plain.cr new file mode 100644 index 000000000..dcc3588e1 --- /dev/null +++ b/src/targets/crystal/native/fixtures/text-plain.cr @@ -0,0 +1,10 @@ +require "http/client" + +url = "https://httpbin.org/anything" +headers = HTTP::Headers{ + "content-type" => "text/plain" +} +reqBody = "Hello World" + +response = HTTP::Client.post url, headers: headers, body: reqBody +puts response.body \ No newline at end of file diff --git a/src/targets/crystal/target.ts b/src/targets/crystal/target.ts new file mode 100644 index 000000000..0c53c2627 --- /dev/null +++ b/src/targets/crystal/target.ts @@ -0,0 +1,14 @@ +import type { Target } from '../index.js'; + +import { native } from './native/client.js'; + +export const crystal: Target = { + info: { + key: 'crystal', + title: 'Crystal', + default: 'native', + }, + clientsById: { + native, + }, +}; diff --git a/src/targets/go/native/client.test.ts b/src/targets/go/native/client.test.ts index 24e78068d..9ff502778 100644 --- a/src/targets/go/native/client.test.ts +++ b/src/targets/go/native/client.test.ts @@ -39,5 +39,13 @@ runCustomFixtures({ }, expected: 'timeout-option.go', }, + { + it: 'should support insecureSkipVerify option', + input: request.log.entries[0].request as Request, + options: { + insecureSkipVerify: true, + }, + expected: 'insecure-skip-verify.go', + }, ], }); diff --git a/src/targets/go/native/client.ts b/src/targets/go/native/client.ts index 54ebd5284..7bd181cd1 100644 --- a/src/targets/go/native/client.ts +++ b/src/targets/go/native/client.ts @@ -14,6 +14,7 @@ import { escapeForDoubleQuotes } from '../../../helpers/escape.js'; export interface GoNativeOptions { checkErrors?: boolean; + insecureSkipVerify?: boolean; printBody?: boolean; showBoilerplate?: boolean; timeout?: number; @@ -30,7 +31,13 @@ export const native: Client = { convert: ({ postData, method, allHeaders, fullUrl }, options = {}) => { const { blank, push, join } = new CodeBuilder({ indent: '\t' }); - const { showBoilerplate = true, checkErrors = false, printBody = true, timeout = -1 } = options; + const { + showBoilerplate = true, + checkErrors = false, + printBody = true, + timeout = -1, + insecureSkipVerify = false, + } = options; const errorPlaceholder = checkErrors ? 'err' : '_'; @@ -55,6 +62,10 @@ export const native: Client = { push('"time"', indent); } + if (insecureSkipVerify) { + push('"crypto/tls"', indent); + } + if (postData.text) { push('"strings"', indent); } @@ -71,9 +82,15 @@ export const native: Client = { blank(); } + // Create an insecure transport for the client + if (insecureSkipVerify) { + push('insecureTransport := http.DefaultTransport.(*http.Transport).Clone()', indent); + push('insecureTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}', indent); + } + // Create client const hasTimeout = timeout > 0; - const hasClient = hasTimeout; + const hasClient = hasTimeout || insecureSkipVerify; const client = hasClient ? 'client' : 'http.DefaultClient'; if (hasClient) { @@ -83,6 +100,10 @@ export const native: Client = { push(`Timeout: time.Duration(${timeout} * time.Second),`, indent + 1); } + if (insecureSkipVerify) { + push('Transport: insecureTransport,', indent + 1); + } + push('}', indent); blank(); } diff --git a/src/targets/go/native/fixtures/insecure-skip-verify.go b/src/targets/go/native/fixtures/insecure-skip-verify.go new file mode 100644 index 000000000..b0ae24057 --- /dev/null +++ b/src/targets/go/native/fixtures/insecure-skip-verify.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + "crypto/tls" + "strings" + "net/http" + "io" +) + +func main() { + + insecureTransport := http.DefaultTransport.(*http.Transport).Clone() + insecureTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + client := http.Client{ + Transport: insecureTransport, + } + + url := "https://httpbin.org/anything?foo=bar&foo=baz&baz=abc&key=value" + + payload := strings.NewReader("foo=bar") + + req, _ := http.NewRequest("POST", url, payload) + + req.Header.Add("cookie", "foo=bar; bar=baz") + req.Header.Add("accept", "application/json") + req.Header.Add("content-type", "application/x-www-form-urlencoded") + + res, _ := client.Do(req) + + defer res.Body.Close() + body, _ := io.ReadAll(res.Body) + + fmt.Println(string(body)) + +} \ No newline at end of file diff --git a/src/targets/index.ts b/src/targets/index.ts index 5a60b7833..621273fd9 100644 --- a/src/targets/index.ts +++ b/src/targets/index.ts @@ -4,6 +4,7 @@ import type { Merge } from 'type-fest'; import { c } from './c/target.js'; import { clojure } from './clojure/target.js'; +import { crystal } from './crystal/target.js'; import { csharp } from './csharp/target.js'; import { go } from './go/target.js'; import { http } from './http/target.js'; @@ -19,6 +20,7 @@ import { powershell } from './powershell/target.js'; import { python } from './python/target.js'; import { r } from './r/target.js'; import { ruby } from './ruby/target.js'; +import { rust } from './rust/target.js'; import { shell } from './shell/target.js'; import { swift } from './swift/target.js'; @@ -40,6 +42,7 @@ export interface ClientInfo = Record> * @example `.js` */ extname: Extension; + /** * Retrieve or generate a command to install the client. * @@ -103,6 +106,7 @@ export interface Target { type supportedTargets = | 'c' | 'clojure' + | 'crystal' | 'csharp' | 'go' | 'http' @@ -118,12 +122,14 @@ type supportedTargets = | 'python' | 'r' | 'ruby' + | 'rust' | 'shell' | 'swift'; export const targets: Record = { c, clojure, + crystal, csharp, go, http, @@ -139,6 +145,7 @@ export const targets: Record = { python, r, ruby, + rust, shell, swift, }; diff --git a/src/targets/javascript/axios/client.ts b/src/targets/javascript/axios/client.ts index dc2f0803d..6290bc444 100644 --- a/src/targets/javascript/axios/client.ts +++ b/src/targets/javascript/axios/client.ts @@ -97,10 +97,12 @@ export const axios: Client = { push(`const options = ${optionString};`); blank(); - push('axios'); - push('.request(options)', 1); - push('.then(res => console.log(res.data))', 1); - push('.catch(err => console.error(err));', 1); + push('try {'); + push('const { data } = await axios.request(options);', 1); + push('console.log(data);', 1); + push('} catch (error) {'); + push('console.error(error);', 1); + push('}'); return join(); }, diff --git a/src/targets/javascript/axios/fixtures/application-form-encoded.js b/src/targets/javascript/axios/fixtures/application-form-encoded.js index 1a83ea542..b191cf34b 100644 --- a/src/targets/javascript/axios/fixtures/application-form-encoded.js +++ b/src/targets/javascript/axios/fixtures/application-form-encoded.js @@ -11,7 +11,9 @@ const options = { data: encodedParams, }; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/application-json.js b/src/targets/javascript/axios/fixtures/application-json.js index 98903a65a..0506f45a3 100644 --- a/src/targets/javascript/axios/fixtures/application-json.js +++ b/src/targets/javascript/axios/fixtures/application-json.js @@ -14,7 +14,9 @@ const options = { } }; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/cookies.js b/src/targets/javascript/axios/fixtures/cookies.js index 7e9cf7ae3..da7df7157 100644 --- a/src/targets/javascript/axios/fixtures/cookies.js +++ b/src/targets/javascript/axios/fixtures/cookies.js @@ -6,7 +6,9 @@ const options = { headers: {cookie: 'foo=bar; bar=baz'} }; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/custom-method.js b/src/targets/javascript/axios/fixtures/custom-method.js index 4142f5977..5ec83924a 100644 --- a/src/targets/javascript/axios/fixtures/custom-method.js +++ b/src/targets/javascript/axios/fixtures/custom-method.js @@ -2,7 +2,9 @@ import axios from 'axios'; const options = {method: 'PROPFIND', url: 'https://httpbin.org/anything'}; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/full.js b/src/targets/javascript/axios/fixtures/full.js index 014bd7340..361a213df 100644 --- a/src/targets/javascript/axios/fixtures/full.js +++ b/src/targets/javascript/axios/fixtures/full.js @@ -15,7 +15,9 @@ const options = { data: encodedParams, }; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/headers.js b/src/targets/javascript/axios/fixtures/headers.js index 8026a1ee9..8be401ea9 100644 --- a/src/targets/javascript/axios/fixtures/headers.js +++ b/src/targets/javascript/axios/fixtures/headers.js @@ -11,7 +11,9 @@ const options = { } }; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/http-insecure.js b/src/targets/javascript/axios/fixtures/http-insecure.js index 0512e2df7..3d4878611 100644 --- a/src/targets/javascript/axios/fixtures/http-insecure.js +++ b/src/targets/javascript/axios/fixtures/http-insecure.js @@ -2,7 +2,9 @@ import axios from 'axios'; const options = {method: 'GET', url: 'http://httpbin.org/anything'}; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/jsonObj-multiline.js b/src/targets/javascript/axios/fixtures/jsonObj-multiline.js index 7d41fbc52..394b277fb 100644 --- a/src/targets/javascript/axios/fixtures/jsonObj-multiline.js +++ b/src/targets/javascript/axios/fixtures/jsonObj-multiline.js @@ -7,7 +7,9 @@ const options = { data: {foo: 'bar'} }; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/jsonObj-null-value.js b/src/targets/javascript/axios/fixtures/jsonObj-null-value.js index 3731d644d..cbfa69da3 100644 --- a/src/targets/javascript/axios/fixtures/jsonObj-null-value.js +++ b/src/targets/javascript/axios/fixtures/jsonObj-null-value.js @@ -7,7 +7,9 @@ const options = { data: {foo: null} }; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/multipart-data.js b/src/targets/javascript/axios/fixtures/multipart-data.js index bb36ba381..dbec900f7 100644 --- a/src/targets/javascript/axios/fixtures/multipart-data.js +++ b/src/targets/javascript/axios/fixtures/multipart-data.js @@ -11,7 +11,9 @@ const options = { data: '[form]' }; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/multipart-file.js b/src/targets/javascript/axios/fixtures/multipart-file.js index 6579bbe8c..10b629768 100644 --- a/src/targets/javascript/axios/fixtures/multipart-file.js +++ b/src/targets/javascript/axios/fixtures/multipart-file.js @@ -10,7 +10,9 @@ const options = { data: '[form]' }; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/multipart-form-data-no-params.js b/src/targets/javascript/axios/fixtures/multipart-form-data-no-params.js index 57e424c87..906318e19 100644 --- a/src/targets/javascript/axios/fixtures/multipart-form-data-no-params.js +++ b/src/targets/javascript/axios/fixtures/multipart-form-data-no-params.js @@ -6,7 +6,9 @@ const options = { headers: {'Content-Type': 'multipart/form-data'} }; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/multipart-form-data.js b/src/targets/javascript/axios/fixtures/multipart-form-data.js index ec40b9e54..8b095dfb2 100644 --- a/src/targets/javascript/axios/fixtures/multipart-form-data.js +++ b/src/targets/javascript/axios/fixtures/multipart-form-data.js @@ -10,7 +10,9 @@ const options = { data: '[form]' }; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/nested.js b/src/targets/javascript/axios/fixtures/nested.js index 3fffb9755..b961311f1 100644 --- a/src/targets/javascript/axios/fixtures/nested.js +++ b/src/targets/javascript/axios/fixtures/nested.js @@ -6,7 +6,9 @@ const options = { params: {'foo[bar]': 'baz,zap', fiz: 'buz', key: 'value'} }; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/postdata-malformed.js b/src/targets/javascript/axios/fixtures/postdata-malformed.js index f40deb9ed..a6b314ab9 100644 --- a/src/targets/javascript/axios/fixtures/postdata-malformed.js +++ b/src/targets/javascript/axios/fixtures/postdata-malformed.js @@ -6,7 +6,9 @@ const options = { headers: {'content-type': 'application/json'} }; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/query-encoded.js b/src/targets/javascript/axios/fixtures/query-encoded.js index 489c9927e..9b7fca824 100644 --- a/src/targets/javascript/axios/fixtures/query-encoded.js +++ b/src/targets/javascript/axios/fixtures/query-encoded.js @@ -9,7 +9,9 @@ const options = { } }; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/query.js b/src/targets/javascript/axios/fixtures/query.js index 39cca5992..3f5c77702 100644 --- a/src/targets/javascript/axios/fixtures/query.js +++ b/src/targets/javascript/axios/fixtures/query.js @@ -6,7 +6,9 @@ const options = { params: {foo: ['bar', 'baz'], baz: 'abc', key: 'value'} }; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/short.js b/src/targets/javascript/axios/fixtures/short.js index ba835ded4..173bbd728 100644 --- a/src/targets/javascript/axios/fixtures/short.js +++ b/src/targets/javascript/axios/fixtures/short.js @@ -2,7 +2,9 @@ import axios from 'axios'; const options = {method: 'GET', url: 'https://httpbin.org/anything'}; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/text-plain.js b/src/targets/javascript/axios/fixtures/text-plain.js index dbe78d903..b37646cda 100644 --- a/src/targets/javascript/axios/fixtures/text-plain.js +++ b/src/targets/javascript/axios/fixtures/text-plain.js @@ -7,7 +7,9 @@ const options = { data: 'Hello World' }; -axios - .request(options) - .then(res => console.log(res.data)) - .catch(err => console.error(err)); \ No newline at end of file +try { + const { data } = await axios.request(options); + console.log(data); +} catch (error) { + console.error(error); +} \ No newline at end of file diff --git a/src/targets/node/native/client.test.ts b/src/targets/node/native/client.test.ts new file mode 100644 index 000000000..5f354b91e --- /dev/null +++ b/src/targets/node/native/client.test.ts @@ -0,0 +1,19 @@ +import type { Request } from '../../../index.js'; + +import httpInsecure from '../../../fixtures/requests/http-insecure.cjs'; +import { runCustomFixtures } from '../../../fixtures/runCustomFixtures'; + +runCustomFixtures({ + targetId: 'node', + clientId: 'native', + tests: [ + { + it: 'should support the insecureSkipVerify option', + input: httpInsecure.log.entries[0].request as Request, + options: { + insecureSkipVerify: true, + }, + expected: 'insecure-skip-verify.cjs', + }, + ], +}); diff --git a/src/targets/node/native/fixtures/insecure-skip-verify.cjs b/src/targets/node/native/fixtures/insecure-skip-verify.cjs new file mode 100644 index 000000000..c39f9a68e --- /dev/null +++ b/src/targets/node/native/fixtures/insecure-skip-verify.cjs @@ -0,0 +1,24 @@ +const http = require('http'); + +const options = { + method: 'GET', + hostname: 'httpbin.org', + port: null, + path: '/anything', + headers: {} +}; + +const req = http.request(options, function (res) { + const chunks = []; + + res.on('data', function (chunk) { + chunks.push(chunk); + }); + + res.on('end', function () { + const body = Buffer.concat(chunks); + console.log(body.toString()); + }); +}); + +req.end(); \ No newline at end of file diff --git a/src/targets/ruby/faraday/client.ts b/src/targets/ruby/faraday/client.ts new file mode 100644 index 000000000..2e184b8e4 --- /dev/null +++ b/src/targets/ruby/faraday/client.ts @@ -0,0 +1,109 @@ +import type { Client } from '../../index.js'; + +import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForSingleQuotes } from '../../../helpers/escape'; + +export const faraday: Client = { + info: { + key: 'faraday', + title: 'faraday', + link: 'https://github.com/lostisland/faraday', + description: 'Faraday HTTP client', + extname: '.rb', + }, + convert: ({ uriObj, queryObj, method: rawMethod, postData, allHeaders }) => { + const { push, blank, join } = new CodeBuilder(); + + // To support custom methods we check for the supported methods + // and if doesn't exist then we build a custom class for it + const method = rawMethod.toUpperCase(); + const methods = [ + 'GET', + 'POST', + 'HEAD', + 'DELETE', + 'PATCH', + 'PUT', + 'OPTIONS', + 'COPY', + 'LOCK', + 'UNLOCK', + 'MOVE', + 'TRACE', + ]; + + if (!methods.includes(method)) { + push(`# Faraday cannot currently run ${method} requests. Please use another client.`); + return join(); + } + + push("require 'faraday'"); + blank(); + + // Write body to beginning of script + if (postData.mimeType === 'application/x-www-form-urlencoded') { + if (postData.params) { + push(`data = {`); + postData.params.forEach(param => { + push(` :${param.name} => ${JSON.stringify(param.value)},`); + }); + push(`}`); + blank(); + } + } + + push(`conn = Faraday.new(`); + push(` url: '${uriObj.protocol}//${uriObj.host}',`); + if (allHeaders['content-type'] || allHeaders['Content-Type']) { + push(` headers: {'Content-Type' => '${allHeaders['content-type'] || allHeaders['Content-Type']}'}`); + } + push(`)`); + + blank(); + push(`response = conn.${method.toLowerCase()}('${uriObj.pathname}') do |req|`); + + const headers = Object.keys(allHeaders); + if (headers.length) { + headers.forEach(key => { + if (key.toLowerCase() !== 'content-type') { + push(` req.headers['${key}'] = '${escapeForSingleQuotes(allHeaders[key])}'`); + } + }); + } + + Object.keys(queryObj).forEach(name => { + const value = queryObj[name]; + if (Array.isArray(value)) { + push(` req.params['${name}'] = ${JSON.stringify(value)}`); + } else { + push(` req.params['${name}'] = '${value}'`); + } + }); + + switch (postData.mimeType) { + case 'application/x-www-form-urlencoded': + if (postData.params) { + push(` req.body = URI.encode_www_form(data)`); + } + break; + + case 'application/json': + if (postData.jsonObj) { + push(` req.body = ${JSON.stringify(postData.text)}`); + } + break; + + default: + if (postData.text) { + push(` req.body = ${JSON.stringify(postData.text)}`); + } + } + + push('end'); + blank(); + push('puts response.status'); + push('puts response.body'); + + return join(); + }, +}; diff --git a/src/targets/ruby/faraday/fixtures/application-form-encoded.rb b/src/targets/ruby/faraday/fixtures/application-form-encoded.rb new file mode 100644 index 000000000..a5676347a --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/application-form-encoded.rb @@ -0,0 +1,18 @@ +require 'faraday' + +data = { + :foo => "bar", + :hello => "world", +} + +conn = Faraday.new( + url: 'https://httpbin.org', + headers: {'Content-Type' => 'application/x-www-form-urlencoded'} +) + +response = conn.post('/anything') do |req| + req.body = URI.encode_www_form(data) +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/application-json.rb b/src/targets/ruby/faraday/fixtures/application-json.rb new file mode 100644 index 000000000..66794f18b --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/application-json.rb @@ -0,0 +1,13 @@ +require 'faraday' + +conn = Faraday.new( + url: 'https://httpbin.org', + headers: {'Content-Type' => 'application/json'} +) + +response = conn.post('/anything') do |req| + req.body = "{\"number\":1,\"string\":\"f\\\"oo\",\"arr\":[1,2,3],\"nested\":{\"a\":\"b\"},\"arr_mix\":[1,\"a\",{\"arr_mix_nested\":[]}],\"boolean\":false}" +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/cookies.rb b/src/targets/ruby/faraday/fixtures/cookies.rb new file mode 100644 index 000000000..2376192b9 --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/cookies.rb @@ -0,0 +1,12 @@ +require 'faraday' + +conn = Faraday.new( + url: 'https://httpbin.org', +) + +response = conn.get('/cookies') do |req| + req.headers['cookie'] = 'foo=bar; bar=baz' +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/custom-method.rb b/src/targets/ruby/faraday/fixtures/custom-method.rb new file mode 100644 index 000000000..213ada538 --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/custom-method.rb @@ -0,0 +1 @@ +# Faraday cannot currently run PROPFIND requests. Please use another client. \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/full.rb b/src/targets/ruby/faraday/fixtures/full.rb new file mode 100644 index 000000000..e1cbc05a0 --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/full.rb @@ -0,0 +1,22 @@ +require 'faraday' + +data = { + :foo => "bar", +} + +conn = Faraday.new( + url: 'https://httpbin.org', + headers: {'Content-Type' => 'application/x-www-form-urlencoded'} +) + +response = conn.post('/anything') do |req| + req.headers['cookie'] = 'foo=bar; bar=baz' + req.headers['accept'] = 'application/json' + req.params['foo'] = ["bar","baz"] + req.params['baz'] = 'abc' + req.params['key'] = 'value' + req.body = URI.encode_www_form(data) +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/headers.rb b/src/targets/ruby/faraday/fixtures/headers.rb new file mode 100644 index 000000000..6ff0b6109 --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/headers.rb @@ -0,0 +1,15 @@ +require 'faraday' + +conn = Faraday.new( + url: 'https://httpbin.org', +) + +response = conn.get('/headers') do |req| + req.headers['accept'] = 'application/json' + req.headers['x-foo'] = 'Bar' + req.headers['x-bar'] = 'Foo' + req.headers['quoted-value'] = '"quoted" \'string\'' +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/http-insecure.rb b/src/targets/ruby/faraday/fixtures/http-insecure.rb new file mode 100644 index 000000000..70bded946 --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/http-insecure.rb @@ -0,0 +1,11 @@ +require 'faraday' + +conn = Faraday.new( + url: 'http://httpbin.org', +) + +response = conn.get('/anything') do |req| +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/jsonObj-multiline.rb b/src/targets/ruby/faraday/fixtures/jsonObj-multiline.rb new file mode 100644 index 000000000..3decb7b2a --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/jsonObj-multiline.rb @@ -0,0 +1,13 @@ +require 'faraday' + +conn = Faraday.new( + url: 'https://httpbin.org', + headers: {'Content-Type' => 'application/json'} +) + +response = conn.post('/anything') do |req| + req.body = "{\n \"foo\": \"bar\"\n}" +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/jsonObj-null-value.rb b/src/targets/ruby/faraday/fixtures/jsonObj-null-value.rb new file mode 100644 index 000000000..9b4dcab62 --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/jsonObj-null-value.rb @@ -0,0 +1,13 @@ +require 'faraday' + +conn = Faraday.new( + url: 'https://httpbin.org', + headers: {'Content-Type' => 'application/json'} +) + +response = conn.post('/anything') do |req| + req.body = "{\"foo\":null}" +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/multipart-data.rb b/src/targets/ruby/faraday/fixtures/multipart-data.rb new file mode 100644 index 000000000..93bf26f6b --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/multipart-data.rb @@ -0,0 +1,13 @@ +require 'faraday' + +conn = Faraday.new( + url: 'https://httpbin.org', + headers: {'Content-Type' => 'multipart/form-data; boundary=---011000010111000001101001'} +) + +response = conn.post('/anything') do |req| + req.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"src/fixtures/files/hello.txt\"\r\nContent-Type: text/plain\r\n\r\nHello World\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bar\"\r\n\r\nBonjour le monde\r\n-----011000010111000001101001--" +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/multipart-file.rb b/src/targets/ruby/faraday/fixtures/multipart-file.rb new file mode 100644 index 000000000..222b8bb69 --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/multipart-file.rb @@ -0,0 +1,13 @@ +require 'faraday' + +conn = Faraday.new( + url: 'https://httpbin.org', + headers: {'Content-Type' => 'multipart/form-data; boundary=---011000010111000001101001'} +) + +response = conn.post('/anything') do |req| + req.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"src/fixtures/files/hello.txt\"\r\nContent-Type: text/plain\r\n\r\n\r\n-----011000010111000001101001--" +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/multipart-form-data-no-params.rb b/src/targets/ruby/faraday/fixtures/multipart-form-data-no-params.rb new file mode 100644 index 000000000..c4ee6cf77 --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/multipart-form-data-no-params.rb @@ -0,0 +1,12 @@ +require 'faraday' + +conn = Faraday.new( + url: 'https://httpbin.org', + headers: {'Content-Type' => 'multipart/form-data'} +) + +response = conn.post('/anything') do |req| +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/multipart-form-data.rb b/src/targets/ruby/faraday/fixtures/multipart-form-data.rb new file mode 100644 index 000000000..1f0e800f2 --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/multipart-form-data.rb @@ -0,0 +1,13 @@ +require 'faraday' + +conn = Faraday.new( + url: 'https://httpbin.org', + headers: {'Content-Type' => 'multipart/form-data; boundary=---011000010111000001101001'} +) + +response = conn.post('/anything') do |req| + req.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\nbar\r\n-----011000010111000001101001--" +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/nested.rb b/src/targets/ruby/faraday/fixtures/nested.rb new file mode 100644 index 000000000..ba622bed6 --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/nested.rb @@ -0,0 +1,14 @@ +require 'faraday' + +conn = Faraday.new( + url: 'https://httpbin.org', +) + +response = conn.get('/anything') do |req| + req.params['foo[bar]'] = 'baz,zap' + req.params['fiz'] = 'buz' + req.params['key'] = 'value' +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/postdata-malformed.rb b/src/targets/ruby/faraday/fixtures/postdata-malformed.rb new file mode 100644 index 000000000..c2315684e --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/postdata-malformed.rb @@ -0,0 +1,12 @@ +require 'faraday' + +conn = Faraday.new( + url: 'https://httpbin.org', + headers: {'Content-Type' => 'application/json'} +) + +response = conn.post('/anything') do |req| +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/query-encoded.rb b/src/targets/ruby/faraday/fixtures/query-encoded.rb new file mode 100644 index 000000000..9d19a55a8 --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/query-encoded.rb @@ -0,0 +1,13 @@ +require 'faraday' + +conn = Faraday.new( + url: 'https://httpbin.org', +) + +response = conn.get('/anything') do |req| + req.params['startTime'] = '2019-06-13T19%3A08%3A25.455Z' + req.params['endTime'] = '2015-09-15T14%3A00%3A12-04%3A00' +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/query.rb b/src/targets/ruby/faraday/fixtures/query.rb new file mode 100644 index 000000000..6f50a3520 --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/query.rb @@ -0,0 +1,14 @@ +require 'faraday' + +conn = Faraday.new( + url: 'https://httpbin.org', +) + +response = conn.get('/anything') do |req| + req.params['foo'] = ["bar","baz"] + req.params['baz'] = 'abc' + req.params['key'] = 'value' +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/short.rb b/src/targets/ruby/faraday/fixtures/short.rb new file mode 100644 index 000000000..71e9e11d6 --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/short.rb @@ -0,0 +1,11 @@ +require 'faraday' + +conn = Faraday.new( + url: 'https://httpbin.org', +) + +response = conn.get('/anything') do |req| +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/faraday/fixtures/text-plain.rb b/src/targets/ruby/faraday/fixtures/text-plain.rb new file mode 100644 index 000000000..d5cae49cf --- /dev/null +++ b/src/targets/ruby/faraday/fixtures/text-plain.rb @@ -0,0 +1,13 @@ +require 'faraday' + +conn = Faraday.new( + url: 'https://httpbin.org', + headers: {'Content-Type' => 'text/plain'} +) + +response = conn.post('/anything') do |req| + req.body = "Hello World" +end + +puts response.status +puts response.body \ No newline at end of file diff --git a/src/targets/ruby/native/client.test.ts b/src/targets/ruby/native/client.test.ts new file mode 100644 index 000000000..ad2ce5474 --- /dev/null +++ b/src/targets/ruby/native/client.test.ts @@ -0,0 +1,19 @@ +import type { Request } from '../../../index.js'; + +import httpInsecure from '../../../fixtures/requests/http-insecure.cjs'; +import { runCustomFixtures } from '../../../fixtures/runCustomFixtures'; + +runCustomFixtures({ + targetId: 'ruby', + clientId: 'native', + tests: [ + { + it: 'should support the insecureSkipVerify option', + input: httpInsecure.log.entries[0].request as Request, + options: { + insecureSkipVerify: true, + }, + expected: 'insecure-skip-verify.rb', + }, + ], +}); diff --git a/src/targets/ruby/native/fixtures/insecure-skip-verify.rb b/src/targets/ruby/native/fixtures/insecure-skip-verify.rb index 14ee76567..54ce4e7d3 100644 --- a/src/targets/ruby/native/fixtures/insecure-skip-verify.rb +++ b/src/targets/ruby/native/fixtures/insecure-skip-verify.rb @@ -1,11 +1,9 @@ require 'uri' require 'net/http' -url = URI("https://httpbin.org/anything") +url = URI("http://httpbin.org/anything") http = Net::HTTP.new(url.host, url.port) -http.use_ssl = true -http.verify_mode = OpenSSL::SSL::VERIFY_NONE request = Net::HTTP::Get.new(url) diff --git a/src/targets/ruby/target.ts b/src/targets/ruby/target.ts index 132efbb0a..05611e426 100644 --- a/src/targets/ruby/target.ts +++ b/src/targets/ruby/target.ts @@ -1,5 +1,6 @@ import type { Target } from '../index.js'; +import { faraday } from './faraday/client.js'; import { native } from './native/client.js'; export const ruby: Target = { @@ -10,5 +11,6 @@ export const ruby: Target = { }, clientsById: { native, + faraday, }, }; diff --git a/src/targets/rust/helpers.ts b/src/targets/rust/helpers.ts new file mode 100644 index 000000000..8060375fb --- /dev/null +++ b/src/targets/rust/helpers.ts @@ -0,0 +1,73 @@ +function concatValues( + concatType: 'array' | 'object', + values: any, + pretty: boolean, + indentation: string, + indentLevel: number, +): string { + const currentIndent = indentation.repeat(indentLevel); + const closingBraceIndent = indentation.repeat(indentLevel - 1); + const join = pretty ? `,\n${currentIndent}` : ', '; + const openingBrace = concatType === 'object' ? 'json!({' : '('; + const closingBrace = concatType === 'object' ? '})' : ')'; + + if (pretty) { + return `${openingBrace}\n${currentIndent}${values.join(join)}\n${closingBraceIndent}${closingBrace}`; + } + + return `${openingBrace}${values.join(join)}${closingBrace}`; +} + +/** + * Create a valid Rust string of a literal value using serde_json according to its type. + * + * @param {*} value Any Javascript literal + * @param {Object} opts Target options + * @return {string} + */ +export const literalRepresentation = (value: any, opts: Record, indentLevel?: number): any => { + /* + * Note: this version is almost entirely borrowed from the Python client helper. The + * only real modification involves the braces and the types. The helper + * could potentially be parameterised for reuse. + */ + indentLevel = indentLevel === undefined ? 1 : indentLevel + 1; + + switch (Object.prototype.toString.call(value)) { + case '[object Number]': + return value; + + case '[object Array]': { + let pretty = false; + const valuesRep: any = (value as any[]).map(v => { + // Switch to prettify if the value is a dict with more than one key. + if (Object.prototype.toString.call(v) === '[object Object]') { + pretty = Object.keys(v).length > 1; + } + return literalRepresentation(v, opts, indentLevel); + }); + return concatValues('array', valuesRep, pretty, opts.indent, indentLevel); + } + + case '[object Object]': { + const keyValuePairs = []; + // oxlint-disable-next-line guard-for-in + for (const key in value) { + keyValuePairs.push(`"${key}": ${literalRepresentation(value[key], opts, indentLevel)}`); + } + return concatValues('object', keyValuePairs, opts.pretty && keyValuePairs.length > 1, opts.indent, indentLevel); + } + + case '[object Null]': + return 'json!(null)'; + + case '[object Boolean]': + return value ? 'true' : 'false'; + + default: + if (value === null || value === undefined) { + return ''; + } + return `"${value.toString().replace(/"/g, '\\"')}"`; + } +}; diff --git a/src/targets/rust/reqwest/client.ts b/src/targets/rust/reqwest/client.ts new file mode 100644 index 000000000..3852d2639 --- /dev/null +++ b/src/targets/rust/reqwest/client.ts @@ -0,0 +1,226 @@ +/** + * @description + * HTTP code snippet generator for Rust using reqwest + * + * @author + * @Benjscho + * + * for any questions or issues regarding the generated code snippet, please open an issue mentioning the author. + */ +import type { Client } from '../../index.js'; + +import { CodeBuilder } from '../../../helpers/code-builder'; +import { literalRepresentation } from '../helpers'; + +export const reqwest: Client = { + info: { + key: 'reqwest', + title: 'reqwest', + link: 'https://docs.rs/reqwest/latest/reqwest/', + description: 'reqwest HTTP library', + extname: '.rs', + }, + convert: ({ queryObj, url, postData, allHeaders, method }, options) => { + const opts = { + indent: ' ', + pretty: true, + ...options, + }; + + let indentLevel = 0; + + // start snippet + const { push, blank, join, pushToLast, unshift } = new CodeBuilder({ indent: opts.indent }); + + // import reqwest + push('use reqwest;', indentLevel); + blank(); + + // start async main for tokio + push('#[tokio::main]', indentLevel); + push('pub async fn main() {', indentLevel); + indentLevel += 1; + + // add url + push(`let url = "${url}";`, indentLevel); + blank(); + + let hasQuery = false; + // construct query string + if (Object.keys(queryObj).length) { + hasQuery = true; + push('let querystring = [', indentLevel); + indentLevel += 1; + for (const [key, value] of Object.entries(queryObj)) { + push(`("${key}", "${value}"),`, indentLevel); + } + indentLevel -= 1; + push('];', indentLevel); + blank(); + } + + // construct payload + let payload: Record = {}; + const files: Record = {}; + + let hasFiles = false; + let hasForm = false; + let hasBody = false; + let jsonPayload = false; + let isMultipart = false; + switch (postData.mimeType) { + case 'application/json': + if (postData.jsonObj) { + push(`let payload = ${literalRepresentation(postData.jsonObj, opts, indentLevel)};`, indentLevel); + } + jsonPayload = true; + break; + + case 'multipart/form-data': + isMultipart = true; + + if (!postData.params) { + push(`let form = reqwest::multipart::Form::new()`, indentLevel); + push(`.text("", "");`, indentLevel + 1); + break; + } + + payload = {}; + postData.params.forEach(p => { + if (p.fileName) { + files[p.name] = p.fileName; + hasFiles = true; + } else { + payload[p.name] = p.value; + } + }); + + if (hasFiles) { + for (const line of fileToPartString) { + push(line, indentLevel); + } + blank(); + } + push(`let form = reqwest::multipart::Form::new()`, indentLevel); + + for (const [name, fileName] of Object.entries(files)) { + push(`.part("${name}", file_to_part("${fileName}").await)`, indentLevel + 1); + } + for (const [name, value] of Object.entries(payload)) { + push(`.text("${name}", "${value}")`, indentLevel + 1); + } + pushToLast(';'); + + break; + + default: { + if (postData.mimeType === 'application/x-www-form-urlencoded' && postData.paramsObj) { + push(`let payload = ${literalRepresentation(postData.paramsObj, opts, indentLevel)};`, indentLevel); + hasForm = true; + break; + } + + if (postData.text) { + push(`let payload = ${literalRepresentation(postData.text, opts, indentLevel)};`, indentLevel); + hasBody = true; + break; + } + } + } + + if (hasForm || jsonPayload || hasBody) { + unshift(`use serde_json::json;`); + blank(); + } + + let hasHeaders = false; + // construct headers + if (Object.keys(allHeaders).length) { + hasHeaders = true; + push('let mut headers = reqwest::header::HeaderMap::new();', indentLevel); + for (const [key, value] of Object.entries(allHeaders)) { + // Skip setting content-type if there is a file, as this header will + // cause the request to hang, and reqwest will set it for us. + if (key.toLowerCase() === 'content-type' && isMultipart) { + // oxlint-disable-next-line no-continue + continue; + } + push(`headers.insert("${key}", ${literalRepresentation(value, opts)}.parse().unwrap());`, indentLevel); + } + blank(); + } + + // construct client + push('let client = reqwest::Client::new();', indentLevel); + + // construct query + switch (method) { + case 'POST': + push(`let response = client.post(url)`, indentLevel); + break; + + case 'GET': + push(`let response = client.get(url)`, indentLevel); + break; + + default: { + push(`let response = client.request(reqwest::Method::from_str("${method}").unwrap(), url)`, indentLevel); + unshift(`use std::str::FromStr;`); + break; + } + } + + if (hasQuery) { + push(`.query(&querystring)`, indentLevel + 1); + } + + if (isMultipart) { + push(`.multipart(form)`, indentLevel + 1); + } + + if (hasHeaders) { + push(`.headers(headers)`, indentLevel + 1); + } + + if (jsonPayload) { + push(`.json(&payload)`, indentLevel + 1); + } + + if (hasForm) { + push(`.form(&payload)`, indentLevel + 1); + } + + if (hasBody) { + push(`.body(payload)`, indentLevel + 1); + } + + // send query + push('.send()', indentLevel + 1); + push('.await;', indentLevel + 1); + blank(); + + // Print response + push('let results = response.unwrap()', indentLevel); + push('.json::()', indentLevel + 1); + push('.await', indentLevel + 1); + push('.unwrap();', indentLevel + 1); + blank(); + + push('dbg!(results);', indentLevel); + + push('}\n'); + + return join(); + }, +}; + +const fileToPartString = [ + `async fn file_to_part(file_name: &'static str) -> reqwest::multipart::Part {`, + ` let file = tokio::fs::File::open(file_name).await.unwrap();`, + ` let stream = tokio_util::codec::FramedRead::new(file, tokio_util::codec::BytesCodec::new());`, + ` let body = reqwest::Body::wrap_stream(stream);`, + ` reqwest::multipart::Part::stream(body)`, + ` .file_name(file_name)`, + ` .mime_str("text/plain").unwrap()`, + `}`, +]; diff --git a/src/targets/rust/reqwest/fixtures/application-form-encoded.rs b/src/targets/rust/reqwest/fixtures/application-form-encoded.rs new file mode 100644 index 000000000..af37cad17 --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/application-form-encoded.rs @@ -0,0 +1,29 @@ +use serde_json::json; +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/anything"; + + let payload = json!({ + "foo": "bar", + "hello": "world" + }); + + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert("content-type", "application/x-www-form-urlencoded".parse().unwrap()); + + let client = reqwest::Client::new(); + let response = client.post(url) + .headers(headers) + .form(&payload) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/application-json.rs b/src/targets/rust/reqwest/fixtures/application-json.rs new file mode 100644 index 000000000..795df61de --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/application-json.rs @@ -0,0 +1,33 @@ +use serde_json::json; +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/anything"; + + let payload = json!({ + "number": 1, + "string": "f\"oo", + "arr": (1, 2, 3), + "nested": json!({"a": "b"}), + "arr_mix": (1, "a", json!({"arr_mix_nested": ()})), + "boolean": false + }); + + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert("content-type", "application/json".parse().unwrap()); + + let client = reqwest::Client::new(); + let response = client.post(url) + .headers(headers) + .json(&payload) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/cookies.rs b/src/targets/rust/reqwest/fixtures/cookies.rs new file mode 100644 index 000000000..5c27d3988 --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/cookies.rs @@ -0,0 +1,22 @@ +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/cookies"; + + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert("cookie", "foo=bar; bar=baz".parse().unwrap()); + + let client = reqwest::Client::new(); + let response = client.get(url) + .headers(headers) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/custom-method.rs b/src/targets/rust/reqwest/fixtures/custom-method.rs new file mode 100644 index 000000000..e7b200a66 --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/custom-method.rs @@ -0,0 +1,19 @@ +use std::str::FromStr; +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/anything"; + + let client = reqwest::Client::new(); + let response = client.request(reqwest::Method::from_str("PROPFIND").unwrap(), url) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/full.rs b/src/targets/rust/reqwest/fixtures/full.rs new file mode 100644 index 000000000..250e9418f --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/full.rs @@ -0,0 +1,35 @@ +use serde_json::json; +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/anything"; + + let querystring = [ + ("foo", "bar,baz"), + ("baz", "abc"), + ("key", "value"), + ]; + + let payload = json!({"foo": "bar"}); + + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert("cookie", "foo=bar; bar=baz".parse().unwrap()); + headers.insert("accept", "application/json".parse().unwrap()); + headers.insert("content-type", "application/x-www-form-urlencoded".parse().unwrap()); + + let client = reqwest::Client::new(); + let response = client.post(url) + .query(&querystring) + .headers(headers) + .form(&payload) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/headers.rs b/src/targets/rust/reqwest/fixtures/headers.rs new file mode 100644 index 000000000..4a7b2ab2e --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/headers.rs @@ -0,0 +1,25 @@ +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/headers"; + + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert("accept", "application/json".parse().unwrap()); + headers.insert("x-foo", "Bar".parse().unwrap()); + headers.insert("x-bar", "Foo".parse().unwrap()); + headers.insert("quoted-value", "\"quoted\" 'string'".parse().unwrap()); + + let client = reqwest::Client::new(); + let response = client.get(url) + .headers(headers) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/http-insecure.rs b/src/targets/rust/reqwest/fixtures/http-insecure.rs new file mode 100644 index 000000000..008733250 --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/http-insecure.rs @@ -0,0 +1,18 @@ +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "http://httpbin.org/anything"; + + let client = reqwest::Client::new(); + let response = client.get(url) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/jsonObj-multiline.rs b/src/targets/rust/reqwest/fixtures/jsonObj-multiline.rs new file mode 100644 index 000000000..aab0d1069 --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/jsonObj-multiline.rs @@ -0,0 +1,26 @@ +use serde_json::json; +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/anything"; + + let payload = json!({"foo": "bar"}); + + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert("content-type", "application/json".parse().unwrap()); + + let client = reqwest::Client::new(); + let response = client.post(url) + .headers(headers) + .json(&payload) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/jsonObj-null-value.rs b/src/targets/rust/reqwest/fixtures/jsonObj-null-value.rs new file mode 100644 index 000000000..7250e76fc --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/jsonObj-null-value.rs @@ -0,0 +1,26 @@ +use serde_json::json; +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/anything"; + + let payload = json!({"foo": json!(null)}); + + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert("content-type", "application/json".parse().unwrap()); + + let client = reqwest::Client::new(); + let response = client.post(url) + .headers(headers) + .json(&payload) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/multipart-data.rs b/src/targets/rust/reqwest/fixtures/multipart-data.rs new file mode 100644 index 000000000..8efafcdff --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/multipart-data.rs @@ -0,0 +1,34 @@ +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/anything"; + + async fn file_to_part(file_name: &'static str) -> reqwest::multipart::Part { + let file = tokio::fs::File::open(file_name).await.unwrap(); + let stream = tokio_util::codec::FramedRead::new(file, tokio_util::codec::BytesCodec::new()); + let body = reqwest::Body::wrap_stream(stream); + reqwest::multipart::Part::stream(body) + .file_name(file_name) + .mime_str("text/plain").unwrap() + } + + let form = reqwest::multipart::Form::new() + .part("foo", file_to_part("src/fixtures/files/hello.txt").await) + .text("bar", "Bonjour le monde"); + let mut headers = reqwest::header::HeaderMap::new(); + + let client = reqwest::Client::new(); + let response = client.post(url) + .multipart(form) + .headers(headers) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/multipart-file.rs b/src/targets/rust/reqwest/fixtures/multipart-file.rs new file mode 100644 index 000000000..045c39734 --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/multipart-file.rs @@ -0,0 +1,33 @@ +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/anything"; + + async fn file_to_part(file_name: &'static str) -> reqwest::multipart::Part { + let file = tokio::fs::File::open(file_name).await.unwrap(); + let stream = tokio_util::codec::FramedRead::new(file, tokio_util::codec::BytesCodec::new()); + let body = reqwest::Body::wrap_stream(stream); + reqwest::multipart::Part::stream(body) + .file_name(file_name) + .mime_str("text/plain").unwrap() + } + + let form = reqwest::multipart::Form::new() + .part("foo", file_to_part("src/fixtures/files/hello.txt").await); + let mut headers = reqwest::header::HeaderMap::new(); + + let client = reqwest::Client::new(); + let response = client.post(url) + .multipart(form) + .headers(headers) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/multipart-form-data-no-params.rs b/src/targets/rust/reqwest/fixtures/multipart-form-data-no-params.rs new file mode 100644 index 000000000..f253ed357 --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/multipart-form-data-no-params.rs @@ -0,0 +1,24 @@ +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/anything"; + + let form = reqwest::multipart::Form::new() + .text("", ""); + let mut headers = reqwest::header::HeaderMap::new(); + + let client = reqwest::Client::new(); + let response = client.post(url) + .multipart(form) + .headers(headers) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/multipart-form-data.rs b/src/targets/rust/reqwest/fixtures/multipart-form-data.rs new file mode 100644 index 000000000..7496e4107 --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/multipart-form-data.rs @@ -0,0 +1,24 @@ +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/anything"; + + let form = reqwest::multipart::Form::new() + .text("foo", "bar"); + let mut headers = reqwest::header::HeaderMap::new(); + + let client = reqwest::Client::new(); + let response = client.post(url) + .multipart(form) + .headers(headers) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/nested.rs b/src/targets/rust/reqwest/fixtures/nested.rs new file mode 100644 index 000000000..58f0cf2a9 --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/nested.rs @@ -0,0 +1,25 @@ +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/anything"; + + let querystring = [ + ("foo[bar]", "baz,zap"), + ("fiz", "buz"), + ("key", "value"), + ]; + + let client = reqwest::Client::new(); + let response = client.get(url) + .query(&querystring) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/postdata-malformed.rs b/src/targets/rust/reqwest/fixtures/postdata-malformed.rs new file mode 100644 index 000000000..6ef285bc4 --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/postdata-malformed.rs @@ -0,0 +1,22 @@ +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/anything"; + + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert("content-type", "application/json".parse().unwrap()); + + let client = reqwest::Client::new(); + let response = client.post(url) + .headers(headers) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/query-encoded.rs b/src/targets/rust/reqwest/fixtures/query-encoded.rs new file mode 100644 index 000000000..4d3938579 --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/query-encoded.rs @@ -0,0 +1,24 @@ +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/anything"; + + let querystring = [ + ("startTime", "2019-06-13T19%3A08%3A25.455Z"), + ("endTime", "2015-09-15T14%3A00%3A12-04%3A00"), + ]; + + let client = reqwest::Client::new(); + let response = client.get(url) + .query(&querystring) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/query.rs b/src/targets/rust/reqwest/fixtures/query.rs new file mode 100644 index 000000000..915dfc983 --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/query.rs @@ -0,0 +1,25 @@ +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/anything"; + + let querystring = [ + ("foo", "bar,baz"), + ("baz", "abc"), + ("key", "value"), + ]; + + let client = reqwest::Client::new(); + let response = client.get(url) + .query(&querystring) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/short.rs b/src/targets/rust/reqwest/fixtures/short.rs new file mode 100644 index 000000000..fe9a06bdc --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/short.rs @@ -0,0 +1,18 @@ +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/anything"; + + let client = reqwest::Client::new(); + let response = client.get(url) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/reqwest/fixtures/text-plain.rs b/src/targets/rust/reqwest/fixtures/text-plain.rs new file mode 100644 index 000000000..fbb39c9c4 --- /dev/null +++ b/src/targets/rust/reqwest/fixtures/text-plain.rs @@ -0,0 +1,26 @@ +use serde_json::json; +use reqwest; + +#[tokio::main] +pub async fn main() { + let url = "https://httpbin.org/anything"; + + let payload = "Hello World"; + + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert("content-type", "text/plain".parse().unwrap()); + + let client = reqwest::Client::new(); + let response = client.post(url) + .headers(headers) + .body(payload) + .send() + .await; + + let results = response.unwrap() + .json::() + .await + .unwrap(); + + dbg!(results); +} diff --git a/src/targets/rust/target.ts b/src/targets/rust/target.ts new file mode 100644 index 000000000..5c2c18758 --- /dev/null +++ b/src/targets/rust/target.ts @@ -0,0 +1,14 @@ +import type { Target } from '../index.js'; + +import { reqwest } from './reqwest/client.js'; + +export const rust: Target = { + info: { + key: 'rust', + title: 'Rust', + default: 'reqwest', + }, + clientsById: { + reqwest, + }, +}; diff --git a/src/targets/shell/curl/client.test.ts b/src/targets/shell/curl/client.test.ts index d0cf3b742..a1bb7239c 100644 --- a/src/targets/shell/curl/client.test.ts +++ b/src/targets/shell/curl/client.test.ts @@ -2,6 +2,7 @@ import type { Request } from '../../../index.js'; import applicationFormEncoded from '../../../fixtures/requests/application-form-encoded.cjs'; import full from '../../../fixtures/requests/full.cjs'; +import httpInsecure from '../../../fixtures/requests/http-insecure.cjs'; import nested from '../../../fixtures/requests/nested.cjs'; import { runCustomFixtures } from '../../../fixtures/runCustomFixtures.js'; @@ -88,6 +89,14 @@ runCustomFixtures({ options: {}, expected: 'urlencode.sh', }, + { + it: 'should support insecureSkipVerify', + input: httpInsecure.log.entries[0].request as Request, + options: { + insecureSkipVerify: true, + }, + expected: 'insecure-skip-verify.sh', + }, { it: 'should use --data-urlencode for form-urlencoded params with special characters in values', input: { diff --git a/src/targets/shell/curl/client.ts b/src/targets/shell/curl/client.ts index 2dee8cfcd..9a5a73967 100644 --- a/src/targets/shell/curl/client.ts +++ b/src/targets/shell/curl/client.ts @@ -18,6 +18,7 @@ export interface CurlOptions { binary?: boolean; globOff?: boolean; indent?: string | false; + insecureSkipVerify?: boolean; prettifyJson?: boolean; short?: boolean; } @@ -57,7 +58,7 @@ export const curl: Client = { extname: '.sh', }, convert: ({ fullUrl, method, httpVersion, headersObj, allHeaders, postData }, options = {}) => { - const { indent = ' ', short = false, binary = false, globOff = false } = options; + const { binary = false, globOff = false, indent = ' ', insecureSkipVerify = false, short = false } = options; // In the interest of having nicer looking snippets JSON should be indented separately from the // main command argument indentation. @@ -79,6 +80,10 @@ export const curl: Client = { } push(`${arg('url ')}${formattedUrl}`); + if (insecureSkipVerify) { + push(arg('insecure')); + } + if (httpVersion === 'HTTP/1.0') { push(arg('http1.0')); } diff --git a/src/targets/shell/curl/fixtures/insecure-skip-verify.sh b/src/targets/shell/curl/fixtures/insecure-skip-verify.sh new file mode 100644 index 000000000..7f99cd859 --- /dev/null +++ b/src/targets/shell/curl/fixtures/insecure-skip-verify.sh @@ -0,0 +1,3 @@ +curl --request GET \ + --url http://httpbin.org/anything \ + --insecure \ No newline at end of file