Repository: postmanlabs/postman-code-generators
Pull request #683 (2023-03-17 09:34:52)

Code Snippet generation for Rust's reqwest library


Patch:
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index d87f7e9..6000233 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -1,6 +1,11 @@
 name: Test
 
-on: [push, pull_request]
+on:
+  push:
+    branches:
+      - develop
+      - master
+  pull_request:
 
 jobs:
   Unit-Tests:
diff --git a/.gitignore b/.gitignore
index bd1f7aa..2f636f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -64,8 +64,11 @@ typings/
 # next.js build output
 .next
 
-out/
+# Cargo
+target/
 
+# Newman test generated files
+out/
 newmanResponses.json
 dummyFile*.txt
 dummyBinaryFile
\ No newline at end of file
diff --git a/README.md b/README.md
index b43d67a..607cf41 100644
--- a/README.md
+++ b/README.md
@@ -36,15 +36,16 @@ List of supported code generators:
 | NodeJs | Unirest |
 | Objective-C| NSURLSession|
 | OCaml | Cohttp | 
-|PHP | cURL |
-|PHP | Guzzle |
-|PHP | pecl_http |
-|PHP | HTTP_Request2 |
+| PHP | cURL |
+| PHP | Guzzle |
+| PHP | pecl_http |
+| PHP | HTTP_Request2 |
 | PowerShell | RestMethod | 
 | Python | http.client |
 | Python | Requests |
 | R | httr |
 | R | RCurl |
+| Rust | Reqwest |
 | Ruby | Net:HTTP |
 | Shell | Httpie |
 | Shell | wget |
diff --git a/codegens/kotlin-okhttp/lib/okhttp.js b/codegens/kotlin-okhttp/lib/okhttp.js
index 2db6f2d..c8ccd72 100644
--- a/codegens/kotlin-okhttp/lib/okhttp.js
+++ b/codegens/kotlin-okhttp/lib/okhttp.js
@@ -18,8 +18,7 @@ const METHODS_WITHOUT_BODY = ['GET', 'HEAD', 'COPY', 'UNLOCK', 'UNLINK', 'PURGE'
 function makeSnippet (request, indentString, options) {
   let isBodyRequired = !(_.includes(METHODS_WITHOUT_BODY, request.method)),
     snippet = 'val client = OkHttpClient',
-    hasNoOptions = !(options.requestTimeout || options.followRedirects),
-    requestBody;
+    hasNoOptions = !(options.requestTimeout || options.followRedirects);
 
   if (hasNoOptions) {
     snippet += '()\n';
@@ -30,7 +29,7 @@ function makeSnippet (request, indentString, options) {
       snippet += indentString + `.connectTimeout(${options.requestTimeout}, TimeUnit.SECONDS)\n`;
     }
 
-    if (!options.followRedirect) {
+    if (_.get(request, 'protocolProfileBehavior.followRedirects', options.followRedirect) === false) {
       snippet += indentString + '.followRedirects(false)\n';
     }
 
@@ -73,10 +72,12 @@ function makeSnippet (request, indentString, options) {
         formdata: formdataArray
       });
     }
-    requestBody = (request.body ? request.body.toJSON() : {});
+
+    const contentType = parseRequest.parseContentType(request),
+      requestBody = (request.body ? request.body.toJSON() : {});
     //  snippet for creating mediatype object in java based on content-type of request
-    snippet += `val mediaType = "${parseRequest.parseContentType(request)}".toMediaType()\n`;
-    snippet += parseRequest.parseBody(requestBody, indentString, options.trimRequestBody);
+    snippet += `val mediaType = "${contentType}".toMediaType()\n`;
+    snippet += parseRequest.parseBody(requestBody, indentString, options.trimRequestBody, contentType);
   }
 
   snippet += 'val request = Request.Builder()\n';
diff --git a/codegens/kotlin-okhttp/lib/parseRequest.js b/codegens/kotlin-okhttp/lib/parseRequest.js
index 3690e30..72d6357 100644
--- a/codegens/kotlin-okhttp/lib/parseRequest.js
+++ b/codegens/kotlin-okhttp/lib/parseRequest.js
@@ -58,21 +58,38 @@ function parseFormData (requestBody, indentString, trimFields) {
   }, '') + indentString + '.build()';
 }
 
+/**
+ * Parses request object and returns kotlin okhttp code snippet for raw body
+ *
+ * @param {Object} requestBody - JSON object representing body of request
+ * @param {Boolean} trimFields - indicates whether to trim fields of body
+ * @param {String} contentType - content type of request body
+ */
+function parseRawBody (requestBody, trimFields, contentType) {
+  if (contentType && contentType.startsWith('application/json')) {
+    return `val body = ${JSON.stringify(requestBody[requestBody.mode])}.toRequestBody(mediaType)\n`;
+  }
+
+  return `val body = "${sanitize(requestBody[requestBody.mode], trimFields)}".toRequestBody(mediaType)\n`;
+}
+
 /**
  * parses request object and returns java okhttp code snippet for adding request body
  *
  * @param {Object} requestBody - JSON object representing body of request
  * @param {String} indentString - string for indentation
  * @param {Boolean} trimFields - indicates whether to trim fields of body
+ * @param {String} contentType - content type of request body
+ *
  * @returns {String} - code snippet of java okhttp parsed from request object
  */
-function parseBody (requestBody, indentString, trimFields) {
+function parseBody (requestBody, indentString, trimFields, contentType) {
   if (!_.isEmpty(requestBody)) {
     switch (requestBody.mode) {
       case 'urlencoded':
         return `val body = "${parseUrlencode(requestBody, trimFields)}".toRequestBody(mediaType)\n`;
       case 'raw':
-        return `val body = ${JSON.stringify(requestBody[requestBody.mode])}.toRequestBody(mediaType)\n`;
+        return parseRawBody(requestBody, trimFields, contentType);
       case 'graphql':
         // eslint-disable-next-line no-case-declarations
         let query = requestBody[requestBody.mode].query,
diff --git a/codegens/kotlin-okhttp/lib/util.js b/codegens/kotlin-okhttp/lib/util.js
index 4ed8cd8..5724812 100644
--- a/codegens/kotlin-okhttp/lib/util.js
+++ b/codegens/kotlin-okhttp/lib/util.js
@@ -11,7 +11,13 @@ module.exports = {
     if (typeof inputString !== 'string') {
       return '';
     }
-    inputString = inputString.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
+    inputString = inputString
+      .replace(/\\/g, '\\\\')
+      .replace(/"/g, '\\"')
+      .replace(/\$/g, '\\$')
+      .replace(/\n/g, '\\n')
+      .replace(/\r/g, '\\r')
+      .replace(/\t/g, '\\t');
     return trim ? inputString.trim() : inputString;
 
   },
diff --git a/codegens/kotlin-okhttp/package.json b/codegens/kotlin-okhttp/package.json
index 5a0db17..40378aa 100644
--- a/codegens/kotlin-okhttp/package.json
+++ b/codegens/kotlin-okhttp/package.json
@@ -1,12 +1,12 @@
 {
   "name": "@postman/codegen-kotlin-okhttp",
   "version": "0.0.1",
-  "description": "Converts postman request into kotlin ktor code snippet",
+  "description": "Converts postman request into kotlin okhttp code snippet",
   "com_postman_plugin": {
     "type": "code_generator",
-    "lang": "kotlin",
-    "variant": "Ktor",
-    "syntax_mode": "java"
+    "lang": "Kotlin",
+    "variant": "Okhttp",
+    "syntax_mode": "kotlin"
   },
   "main": "index.js",
   "directories": {
diff --git a/codegens/rust-reqwest/.gitignore b/codegens/rust-reqwest/.gitignore
new file mode 100644
index 0000000..b14183e
--- /dev/null
+++ b/codegens/rust-reqwest/.gitignore
@@ -0,0 +1,72 @@
+.DS_Store
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Prevent IDE stuff
+.idea
+.vscode
+*.sublime-*
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+.coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Typescript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+out/
+
+# Cargo
+target/
+src/
+Cargo.toml
+Cargo.lock
\ No newline at end of file
diff --git a/codegens/rust-reqwest/README.md b/codegens/rust-reqwest/README.md
new file mode 100644
index 0000000..119951c
--- /dev/null
+++ b/codegens/rust-reqwest/README.md
@@ -0,0 +1,79 @@
+# Code-Gen: Postman SDK Request -> Rust Snippet Converter
+
+This module is used to convert Postman SDK-Request object into a Rust code snippet.
+
+#### Prerequisites
+To run this repository, ensure that you have NodeJS >= v12.
+
+## Using the Module
+This module exposes two function `convert()` and `getOptions()`
+
+### Convert
+ 
+Convert function sanitizes the inputs, overrides options with the default ones if not provided and return the code snippet in the desired format.
+
+It requires 3 mandatory parameters `request`, `callback` and `options`
+
+* `request` - postman SDK-request object
+
+* `options` is an object with the following properties
+
+    * `indentType` : can be `Tab` or `Space` (default: 'Space')
+    * `indentCount` : Integer denoting the number of tabs/spaces required for indentation, range 0-8 (default : for indentType Tab : 2, for indentType Space : 4)
+    * `trimRequestBody` : Trim request body fields (default: false)
+    These plugin options will be used if no options are passed to the convert function.
+
+* `callback` : callback function with `error` and `snippet` parameters where snippet is the desired output
+
+#### Example
+```javascript
+const sdk = require('postman-collection');
+
+const request = sdk.Request('http://www.google.com'),
+    options = {indentType: 'Tab', indentCount: 4, trimRequestBody: true};
+
+convert(request, options, function (err, snippet) {
+    if (err) {
+        // perform desired action of logging the error
+    }
+    // perform action with the snippet
+});
+```
+
+### getOptions function
+
+This function returns a list of options supported by this codegen.
+
+#### Example
+```js
+const options = getOptions();
+
+console.log(options);
+// output
+// [
+//       {
+//         name: 'Set indentation count',
+//         id: 'indentCount',
+//         type: 'positiveInteger',
+//         default: 2,
+//         description: 'Set the number of indentation characters to add per code level'
+//       },
+//       ...
+// ]
+```
+
+### Notes
+
+* This module supports all request types of requests which are present in the Postman App.
+
+* Does not handle cookies or proxies and generates a snippet for the base request.
+
+* Snippet generated is supported for Rust 1.0+ versions.
+
+* Does not support if the request body is passed by means of a binary file.
+
+* User ◊needs to enter the absolute path of the file in the snippet. This just picks the relative path in case of file upload in form data body.
+
+### Resources
+
+* [Rust](https://www.rust-lang.org/) official documentation 
diff --git a/codegens/rust-reqwest/index.js b/codegens/rust-reqwest/index.js
new file mode 100644
index 0000000..bb0a047
--- /dev/null
+++ b/codegens/rust-reqwest/index.js
@@ -0,0 +1,1 @@
+module.exports = require('./lib');
diff --git a/codegens/rust-reqwest/lib/index.js b/codegens/rust-reqwest/lib/index.js
new file mode 100644
index 0000000..1d1a7b6
--- /dev/null
+++ b/codegens/rust-reqwest/lib/index.js
@@ -0,0 +1,4 @@
+module.exports = {
+  convert: require('./reqwest').convert,
+  getOptions: require('./reqwest').getOptions
+};
diff --git a/codegens/rust-reqwest/lib/reqwest.js b/codegens/rust-reqwest/lib/reqwest.js
new file mode 100644
index 0000000..4736103
--- /dev/null
+++ b/codegens/rust-reqwest/lib/reqwest.js
@@ -0,0 +1,147 @@
+const _ = require('lodash'),
+  sanitizeOptions = require('./util/sanitize').sanitizeOptions,
+  { parseHeader, parseBody } = require('./util/parseRequest'),
+  { addDefaultContentType, formatFormData } = require('./util/formatRequest'),
+  ALLOWED_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'CONNECT', 'PATH', 'TRACE'];
+
+/**
+ * Returns snippet for Rust reqwest by parsing data from Postman-SDK request object
+ *
+ * @param {Object} request - Postman SDK request object
+ * @param {String} indentation - indentation required for code snippet
+ * @param {Object} options - Options for code generation
+ *
+ * @returns {String} - Rust reqwest code snippet for given request object
+ */
+function makeSnippet (request, indentation, options) {
+  // Build the client - set timeout and redirect policy
+  let snippet = '#[tokio::main]\n';
+  snippet += 'async fn main() -> Result<(), Box<dyn std::error::Error>> {\n';
+  snippet += `${indentation}let client = reqwest::Client::builder()\n`;
+
+  // Disable redirects if option is set
+  if (_.get(request, 'protocolProfileBehavior.followRedirects', options.followRedirect) === false) {
+    snippet += `${indentation.repeat(2)}.redirect(reqwest::redirect::Policy::none())\n`;
+  }
+
+  snippet += `${indentation.repeat(2)}.build()?;\n\n`;
+
+  addDefaultContentType(request);
+  request.body && request.body.mode === 'formdata' && formatFormData(request);
+
+  const body = request.body && request.body.toJSON(),
+    contentType = request.headers.get('Content-Type'),
+    { headerSnippet, requestHeaderSnippet } = parseHeader(request, indentation),
+    { bodySnippet, requestBodySnippet } = parseBody(body, options.trimRequestBody, indentation, contentType);
+
+  snippet += headerSnippet;
+  snippet += bodySnippet;
+
+  // Use short method name if possible
+  let requestSnippet = '';
+  if (ALLOWED_METHODS.includes(request.method)) {
+    requestSnippet += `${indentation}let request = client.request(reqwest::Method::${request.method}, `;
+  }
+  else {
+    requestSnippet += `${indentation}let method = "${request.method}";\n`;
+    requestSnippet += `${indentation}let request = client.request(reqwest::Method::from_bytes(method.as_bytes())?, `;
+  }
+
+  // Add headers and body
+  requestSnippet += `"${request.url.toString()}")\n`;
+  requestSnippet += requestHeaderSnippet;
+  requestSnippet += requestBodySnippet;
+
+  // Set request timeout
+  if (options.requestTimeout !== 0) {
+    requestSnippet += `${indentation.repeat(2)}.timeout(std::time::Duration::from_millis(${options.requestTimeout}))\n`;
+  }
+
+  requestSnippet = requestSnippet.slice(0, -1) + ';\n\n';
+
+  snippet += requestSnippet;
+  snippet += `${indentation}let response = request.send().await?;\n`;
+  snippet += `${indentation}let body = response.text().await?;\n\n`;
+
+  snippet += `${indentation}println!("{}", body);\n\n`;
+  snippet += `${indentation}Ok(())\n}`;
+
+  return snippet;
+}
+
+const self = module.exports = {
+  /**
+     * Used to return options which are specific to a particular plugin
+     *
+     * @returns {Array}
+     */
+  getOptions: function () {
+    return [
+      {
+        name: 'Set indentation count',
+        id: 'indentCount',
+        type: 'positiveInteger',
+        default: 4,
+        description: 'Set the number of indentation characters to add per code level'
+      },
+      {
+        name: 'Set indentation type',
+        id: 'indentType',
+        type: 'enum',
+        availableOptions: ['Tab', 'Space'],
+        default: 'Space',
+        description: 'Select the character used to indent lines of code'
+      },
+      {
+        name: 'Set request timeout',
+        id: 'requestTimeout',
+        type: 'positiveInteger',
+        default: 0,
+        description: 'Set number of milliseconds the request should wait for a response' +
+    ' before timing out (use 0 for infinity)'
+      },
+      {
+        name: 'Follow redirects',
+        id: 'followRedirect',
+        type: 'boolean',
+        default: true,
+        description: 'Automatically follow HTTP redirects'
+      },
+      {
+        name: 'Trim request body fields',
+        id: 'trimRequestBody',
+        type: 'boolean',
+        default: false,
+        description: 'Remove white space and additional lines that may affect the server\'s response'
+      }];
+  },
+
+  /**
+    * Used to convert the postman sdk-request object to rust snippet
+    *
+    * @param  {Object} request - postman SDK-request object
+    * @param  {Object} options
+    * @param  {String} options.indentType - type of indentation eg: Space / Tab (default: Space)
+    * @param  {Number} options.indentCount - frequency of indent (default: 4 for indentType: Space,
+                                                                    default: 1 for indentType: Tab)
+    * @param {Number} options.requestTimeout : time in milli-seconds after which request will bail out
+                                                (default: 0 -> never bail out)
+    * @param {Boolean} options.trimRequestBody : whether to trim request body fields (default: false)
+    * @param {Boolean} options.followRedirect : whether to allow redirects of a request
+    * @param  {Function} callback - function with parameters (error, snippet)
+    */
+  convert: function (request, options, callback) {
+    if (!_.isFunction(callback)) {
+      throw new Error('Rust~reqwest-convert: Callback is not a function');
+    }
+    options = sanitizeOptions(options, self.getOptions());
+
+    //  String representing value of indentation required
+    let indentString;
+
+    indentString = options.indentType === 'Tab' ? '\t' : ' ';
+    indentString = indentString.repeat(options.indentCount);
+
+    return callback(null, makeSnippet(request, indentString, options));
+  }
+};
diff --git a/codegens/rust-reqwest/lib/util/formatRequest.js b/codegens/rust-reqwest/lib/util/formatRequest.js
new file mode 100644
index 0000000..c53b42d
--- /dev/null
+++ b/codegens/rust-reqwest/lib/util/formatRequest.js
@@ -0,0 +1,104 @@
+/**
+ *
+ * @param {Array} array - form data array
+ * @param {String} key - key of form data param
+ * @param {String} type - type of form data param(file/text)
+ * @param {String} val - value/src property of form data param
+ * @param {String} disabled - Boolean denoting whether the param is disabled or not
+ * @param {String} contentType - content type header of the param
+ *
+ * Appends a single param to form data array
+ */
+function addFormParam (array, key, type, val, disabled, contentType) {
+  if (type === 'file') {
+    array.push({
+      key: key,
+      type: type,
+      src: val,
+      disabled: disabled,
+      contentType: contentType
+    });
+  }
+  else {
+    array.push({
+      key: key,
+      type: type,
+      value: val,
+      disabled: disabled,
+      contentType: contentType
+    });
+  }
+}
+
+module.exports = {
+  /**
+    * Adds default content-type header if not present in request
+    *
+    * @param {Object} request - Postman SDK request object
+    */
+  addDefaultContentType: function (request) {
+    if (!request.body || request.headers.has('Content-Type')) {
+      return;
+    }
+
+    if (request.body.mode === 'file') {
+      request.addHeader({
+        key: 'Content-Type',
+        value: 'text/plain'
+      });
+    }
+    else if (request.body.mode === 'graphql') {
+      request.addHeader({
+        key: 'Content-Type',
+        value: 'application/json'
+      });
+    }
+  },
+
+  /**
+   * The following code handles multiple files in the same formdata param.
+   * It removes the form data params where the src property is an array of filepath strings
+   * Splits that array into different form data params with src set as a single filepath string
+   *
+   * @param {Object} request - Postman SDK Request object
+   */
+  formatFormData: function (request) {
+    const formdata = request.body.formdata,
+      formdataArray = [];
+    formdata.members.forEach((param) => {
+      const key = param.key,
+        type = param.type,
+        disabled = param.disabled,
+        contentType = param.contentType;
+      // check if type is file or text
+      if (type === 'file') {
+      // if src is not of type string we check for array(multiple files)
+        if (typeof param.src !== 'string') {
+        // if src is an array(not empty), iterate over it and add files as separate form fields
+          if (Array.isArray(param.src) && param.src.length) {
+            param.src.forEach((filePath) => {
+              addFormParam(formdataArray, key, param.type, filePath, disabled, contentType);
+            });
+          }
+          // if src is not an array or string, or is an empty array, add a placeholder for file path(no files case)
+          else {
+            addFormParam(formdataArray, key, param.type, '/path/to/file', disabled, contentType);
+          }
+        }
+        // if src is string, directly add the param with src as filepath
+        else {
+          addFormParam(formdataArray, key, param.type, param.src, disabled, contentType);
+        }
+      }
+      // if type is text, directly add it to formdata array
+      else {
+        addFormParam(formdataArray, key, param.type, param.value, disabled, contentType);
+      }
+    });
+
+    request.body.update({
+      mode: 'formdata',
+      formdata: formdataArray
+    });
+  }
+};
diff --git a/codegens/rust-reqwest/lib/util/parseRequest.js b/codegens/rust-reqwest/lib/util/parseRequest.js
new file mode 100644
index 0000000..e8dbcf8
--- /dev/null
+++ b/codegens/rust-reqwest/lib/util/parseRequest.js
@@ -0,0 +1,245 @@
+const _ = require('lodash'),
+  path = require('path'),
+  { sanitize } = require('./sanitize');
+
+/**
+ * Parses header of request object and returns code snippet to add headers
+ *
+ * @param {Object} request - Postman SDK request object
+ * @param {String} indentation - indentation required in code snippet
+ *
+ * @typedef {Object} HeaderSnippet - code snippet to add headers to Rust reqwest
+ * @property {String} headerSnippet - code snippet to define headers
+ * @property {String} requestHeaderSnippet - code snippet to add headers to request
+ *
+ * @returns {HeaderSnippet}
+ */
+function parseHeader (request, indentation) {
+  const enabledHeaders = request.getHeaders({ enabled: true });
+  let headerSnippet = '',
+    requestHeaderSnippet = '';
+
+  if (_.isEmpty(enabledHeaders)) {
+    return { headerSnippet, requestHeaderSnippet };
+  }
+
+  headerSnippet += `${indentation}let mut headers = reqwest::header::HeaderMap::new();\n`;
+
+  _.forEach(enabledHeaders, (value, key) => {
+    if (Array.isArray(value)) {
+      value = value.join(', ');
+    }
+
+    headerSnippet += `${indentation}headers.insert("${sanitize(key)}", `;
+    headerSnippet += `"${sanitize(value)}".parse()?);\n`;
+  });
+
+  headerSnippet += '\n';
+  requestHeaderSnippet += `${indentation.repeat(2)}.headers(headers)\n`;
+
+  return { headerSnippet, requestHeaderSnippet };
+}
+
+/**
+ * Parses URLEncoded body to Rust reqwest syntax
+ *
+ * @param {Object} body URLEncoded Body
+ * @param {boolean} trim trim body option
+ * @param {string} indentation The indentation string
+ *
+ * @returns {BodySnippet}
+ */
+function parseURLEncodedBody (body, trim, indentation) {
+  let bodySnippet = `${indentation}let mut params = std::collections::HashMap::new();\n`,
+    requestBodySnippet = `${indentation.repeat(2)}.form(&params)\n`;
+
+  _.forEach(body, function (data) {
+    if (!data.disabled) {
+      bodySnippet += `${indentation}params.insert("${sanitize(data.key, trim)}", `;
+      bodySnippet += `"${sanitize(data.value, trim)}");\n`;
+    }
+  });
+
+  bodySnippet += '\n';
+
+  return { bodySnippet, requestBodySnippet };
+}
+
+/**
+ * Parses raw body to Rust reqwest syntax
+ *
+ * @param {Object} body Raw body
+ * @param {Boolean} trim trim body option
+ * @param {String} indentation The indentation string
+ * @param {String} contentType Content type of the body being sent
+ *
+ * @returns {BodySnippet}
+ */
+function parseRawBody (body, trim, indentation, contentType) {
+  let bodySnippet = '',
+    requestBodySnippet = '';
+
+  // Check if the body is a valid JSON
+  // If it is, then parse it to serde_json::Value
+  // Else, just send it as a string
+  if (contentType && contentType.startsWith('application/json')) {
+    try {
+      const jsonBody = JSON.parse(body),
+        jsonValue = JSON.stringify(jsonBody, null, indentation);
+      bodySnippet += `${indentation}let data = r#"${jsonValue}"#;\n\n`;
+      bodySnippet += `${indentation}let json: serde_json::Value = serde_json::from_str(&data)?;\n\n`;
+      requestBodySnippet += `${indentation.repeat(2)}.json(&json)\n`;
+    }
+    catch (e) {
+      bodySnippet = `${indentation}let data = "${sanitize(body, trim)}";\n\n`;
+      requestBodySnippet += `${indentation.repeat(2)}.body(data)\n`;
+    }
+  }
+  else {
+    bodySnippet = `${indentation}let data = "${sanitize(body, trim)}";\n\n`;
+    requestBodySnippet += `${indentation.repeat(2)}.body(data)\n`;
+  }
+
+  return { bodySnippet, requestBodySnippet };
+}
+
+/**
+ * Parses GraphQL body to Rust reqwest syntax
+ *
+ * @param {Object} body Raw body
+ * @param {Boolean} trim trim body option
+ * @param {String} indentation The indentation string
+ *
+ * @returns {BodySnippet}
+ */
+function parseGraphQL (body, trim, indentation) {
+  let query = body ? sanitize(body.query, trim) : '',
+    bodySnippet = '',
+    requestBodySnippet = '';
+
+  bodySnippet += `${indentation}let data = r#"\n{\n${indentation}"query": "${query}"`;
+  if (body && body.variables) {
+    const variables = trim ? body.variables.trim() : body.variables;
+    bodySnippet += `,\n${indentation}"variables": ${variables}`;
+  }
+  bodySnippet += '\n}\n"#;\n';
+  bodySnippet += `${indentation}let json: serde_json::Value = serde_json::from_str(&data)?;\n\n`;
+  requestBodySnippet += `${indentation.repeat(2)}.json(&json)\n`;
+
+  return { bodySnippet, requestBodySnippet };
+}
+
+/**
+ * Parses Formdata to Rust reqwest syntax
+ *
+ * @param {Object} body FormData body
+ * @param {Boolean} trim trim body option
+ * @param {String} indentation The indentation string
+ */
+function parseFormData (body, trim, indentation) {
+  let beforeSnippet = '',
+    index = 1, // this is used to generate headers for each part
+    bodySnippet = `${indentation}let form = reqwest::multipart::Form::new()\n`,
+    requestBodySnippet = `${indentation.repeat(2)}.multipart(form)\n`;
+
+  _.forEach(body, function (data) {
+    if (!data.disabled) {
+      if (data.type === 'file') {
+
+        const filename = data.src.split(path.sep).pop();
+        bodySnippet += `${indentation.repeat(2)}.part("${sanitize(data.key, trim)}", `;
+        bodySnippet += `reqwest::multipart::Part::bytes(std::fs::read("${sanitize(data.src, trim)}")?)`;
+        bodySnippet += `.file_name("${filename}")`;
+
+        if (data.contentType) {
+          beforeSnippet += `${indentation}let mut form_param${index}_headers = `;
+          beforeSnippet += 'reqwest::header::HeaderMap::new();\n';
+          beforeSnippet += `${indentation}form_param${index}_headers.insert("Content-Type", `;
+          beforeSnippet += `"${sanitize(data.contentType, trim)}".parse()?);\n\n`;
+
+          bodySnippet += `.headers(form_param${index}_headers)`;
+        }
+
+        bodySnippet += ')\n';
+
+        index++;
+      }
+      else if (data.contentType) {
+
+        beforeSnippet += `${indentation}let mut form_param${index}_headers = `;
+        beforeSnippet += 'reqwest::header::HeaderMap::new();\n';
+        beforeSnippet += `${indentation}form_param${index}_headers.insert("Content-Type", `;
+        beforeSnippet += `"${sanitize(data.contentType, trim)}".parse()?);\n\n`;
+
+        bodySnippet += `${indentation.repeat(2)}.part("${sanitize(data.key, trim)}", `;
+        bodySnippet += `reqwest::multipart::Part::text("${sanitize(data.value, trim)}")`;
+        bodySnippet += `.headers(form_param${index}_headers))\n`;
+
+        index++;
+      }
+      else {
+        bodySnippet += `${indentation.repeat(2)}.text("${sanitize(data.key, trim)}", `;
+        bodySnippet += `"${sanitize(data.value, trim)}")\n`;
+      }
+    }
+  });
+
+  bodySnippet = beforeSnippet + bodySnippet.slice(0, -1) + ';\n\n';
+
+  return { bodySnippet, requestBodySnippet };
+}
+
+/**
+ * Parses File body to Rust reqwest syntax
+ *
+ * @param {Object} body File body
+ * @param {String} indentation The indentation string
+ *
+ * @returns {BodySnippet}
+ */
+function parseFileData (body, indentation) {
+  const bodySnippet = `${indentation}let bytes = std::fs::read("${sanitize(body.src)}")?;\n\n`,
+    requestBodySnippet = `${indentation.repeat(2)}.body(bytes)\n`;
+
+  return { bodySnippet, requestBodySnippet };
+}
+
+/**
+ * Parses Body from the Request and returns code snippet to add body
+ *
+ * @param {Object} body body object from request
+ * @param {Boolean} trim trim body option
+ * @param {String} indentation indentation to be added to the snippet
+ * @param {String} contentType Content type of the body being sent
+ *
+ * @typedef {Object} BodySnippet - code snippet to add body to Rust reqwest
+ * @property {String} bodySnippet - code snippet to define body
+ * @property {String} requestBodySnippet - code snippet to add body to request
+ *
+ * @returns {BodySnippet}
+ */
+function parseBody (body, trim, indentation, contentType) {
+  if (!body || _.isEmpty(body)) {
+    return { bodySnippet: '', requestBodySnippet: '' };
+  }
+
+  switch (body.mode) {
+    case 'urlencoded':
+      return parseURLEncodedBody(body.urlencoded, trim, indentation);
+    case 'raw':
+      return parseRawBody(body.raw, trim, indentation, contentType);
+    case 'graphql':
+      return parseGraphQL(body.graphql, trim, indentation);
+    case 'formdata':
+      return parseFormData(body.formdata, trim, indentation);
+    case 'file':
+      return parseFileData(body.file, indentation);
+    default:
+      return parseRawBody(body[body.mode], trim, indentation, contentType);
+  }
+}
+
+module.exports = {
+  parseHeader,
+  parseBody
+};
diff --git a/codegens/rust-reqwest/lib/util/sanitize.js b/codegens/rust-reqwest/lib/util/sanitize.js
new file mode 100644
index 0000000..a047aad
--- /dev/null
+++ b/codegens/rust-reqwest/lib/util/sanitize.js
@@ -0,0 +1,91 @@
+module.exports = {
+  /**
+   * sanitizes input string by handling escape characters eg: converts '''' to '\'\''
+   * and trim input if required
+   *
+   * @param {String} inputString
+   * @param {Boolean} [trim] - indicates whether to trim string or not
+   * @returns {String}
+   */
+  sanitize: function (inputString, trim) {
+    if (typeof inputString !== 'string') {
+      return '';
+    }
+
+    (trim) && (inputString = inputString.trim());
+    return inputString
+      .replace(/\\/g, '\\\\')
+      .replace(/"/g, '\\"')
+      .replace(/\n/g, '\\n')
+      .replace(/\r/g, '\\r')
+      .replace(/\t/g, '\\t');
+  },
+
+  /**
+    * sanitizes input options
+    *
+    * @param {Object} options - Options provided by the user
+    * @param {Array} optionsArray - options array received from getOptions function
+    *
+    * @returns {Object} - Sanitized options object
+    */
+  sanitizeOptions: function (options, optionsArray) {
+    var result = {},
+      defaultOptions = {},
+      id;
+    optionsArray.forEach((option) => {
+      defaultOptions[option.id] = {
+        default: option.default,
+        type: option.type
+      };
+      if (option.type === 'enum') {
+        defaultOptions[option.id].availableOptions = option.availableOptions;
+      }
+    });
+
+    for (id in options) {
+      if (options.hasOwnProperty(id)) {
+        if (defaultOptions[id] === undefined) {
+          continue;
+        }
+        switch (defaultOptions[id].type) {
+          case 'boolean':
+            if (typeof options[id] !== 'boolean') {
+              result[id] = defaultOptions[id].default;
+            }
+            else {
+              result[id] = options[id];
+            }
+            break;
+          case 'positiveInteger':
+            if (typeof options[id] !== 'number' || options[id] < 0) {
+              result[id] = defaultOptions[id].default;
+            }
+            else {
+              result[id] = options[id];
+            }
+            break;
+          case 'enum':
+            if (!defaultOptions[id].availableOptions.includes(options[id])) {
+              result[id] = defaultOptions[id].default;
+            }
+            else {
+              result[id] = options[id];
+            }
+            break;
+          default:
+            result[id] = options[id];
+        }
+      }
+    }
+
+    for (id in defaultOptions) {
+      if (defaultOptions.hasOwnProperty(id)) {
+        if (result[id] === undefined) {
+          result[id] = defaultOptions[id].default;
+        }
+      }
+    }
+    return result;
+  }
+};
diff --git a/codegens/rust-reqwest/package-lock.json b/codegens/rust-reqwest/package-lock.json
new file mode 100644
index 0000000..145b406
--- /dev/null
+++ b/codegens/rust-reqwest/package-lock.json
@@ -0,0 +1,13 @@
+{
+  "name": "@postman/codegen-rust-reqwest",
+  "version": "0.0.1",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+    }
+  }
+}
diff --git a/codegens/rust-reqwest/package.json b/codegens/rust-reqwest/package.json
new file mode 100644
index 0000000..8b9eac3
--- /dev/null
+++ b/codegens/rust-reqwest/package.json
@@ -0,0 +1,36 @@
+{
+  "name": "@postman/codegen-rust-reqwest",
+  "version": "0.0.1",
+  "description": "Generates code snippets for a postman collection using Rust's reqwest library",
+  "com_postman_plugin": {
+    "type": "code_generator",
+    "lang": "Rust",
+    "variant": "reqwest",
+    "syntax_mode": "rust"
+  },
+  "main": "index.js",
+  "directories": {
+    "lib": "lib",
+    "test": "test"
+  },
+  "scripts": {
+    "test": "node npm/test.js",
+    "test-lint": "node npm/test-lint.js",
+    "test-newman": "node npm/test-newman.js",
+    "test-unit": "node npm/test-unit.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": ""
+  },
+  "author": "Postman Labs <help@getpostman.com>",
+  "license": "Apache-2.0",
+  "homepage": "https://github.com/postmanlabs/code-generators/tree/master/codegens/rust-reqwest",
+  "dependencies": {
+    "lodash": "4.17.21"
+  },
+  "devDependencies": {},
+  "engines": {
+    "node": ">=12"
+  }
+}
diff --git a/lib/index.js b/lib/index.js
index 6f319af..b84e531 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -1,4 +1,4 @@
-var sdk = require('postman-collection'),
+const sdk = require('postman-collection'),
   labelList = require('./assets/languageLabels.json'),
   languageMap = require('./assets/languages.js');
 
@@ -11,14 +11,14 @@ module.exports = {
    * @param {Function} callback - callback function with arguments (error, object)
    */
   getOptions (language, variant, callback) {
-    var validCodegen = languageMap.filter((codegen) => {
-      var lang = codegen.lang.trim(),
+    const validCodegen = languageMap.filter((codegen) => {
+      const lang = codegen.lang.trim(),
         currentVariant = codegen.variant.trim();
       return language === lang.toLowerCase() && variant.toLowerCase() === currentVariant.toLowerCase();
     });
 
     validCodegen.forEach((codegen) => {
-      main = codegen.main;
+      const main = codegen.main;
       if (typeof main.getOptions !== 'function') {
         return callback('Codegen~getOptions: getOptions is not a function');
       }
@@ -35,10 +35,10 @@ module.exports = {
    *
    */
   getLanguageList () {
-    var langMap = {},
+    let langMap = {},
       supportedLanguages = [];
     languageMap.forEach((codegen) => {
-      var lang = codegen.lang.trim(),
+      let lang = codegen.lang.trim(),
         syntax_mode = codegen.syntax_mode.trim(),
         variant = codegen.variant.trim();
       lang = lang.toLowerCase();
@@ -84,14 +84,14 @@ module.exports = {
    * @param {Function} callback - callback function with arguments (error, string)
    */
   convert (language, variant, request, options, callback) {
-    var convert, main;
+    let convert, main;
 
     if (!sdk.Request.isRequest(request)) {
       return callback('Codegen~convert: Invalid request');
     }
 
     languageMap.forEach((codegen) => {
-      var lang = codegen.lang.trim(),
+      const lang = codegen.lang.trim(),
         currentVariant = codegen.variant.trim();
       if (language.toLowerCase() === lang.toLowerCase() && variant.toLowerCase() === currentVariant.toLowerCase()) {
         main = codegen.main;
diff --git a/npm/ci-requirements.sh b/npm/ci-requirements.sh
index 722d4ac..98cac20 100755
--- a/npm/ci-requirements.sh
+++ b/npm/ci-requirements.sh
@@ -66,7 +66,7 @@ pushd ./codegens/swift &>/dev/null;
   sudo apt-get update
   sudo apt-get install clang-3.6 libicu-dev libpython2.7 -y
   sudo apt-get install libcurl4 libpython2.7-dev -y
-  sudo wget https://download.swift.org/swift-5.7.3-release/ubuntu2004/swift-5.7.3-RELEASE/swift-5.7.3-RELEASE-ubuntu20.04.tar.gz
+  sudo wget -q https://download.swift.org/swift-5.7.3-release/ubuntu2004/swift-5.7.3-RELEASE/swift-5.7.3-RELEASE-ubuntu20.04.tar.gz
   sudo tar xzf swift-5.7.3-RELEASE-ubuntu20.04.tar.gz
   sudo chmod 777 swift-5.7.3-RELEASE-ubuntu20.04/usr/lib/swift/CoreFoundation/module.map
 popd &>/dev/null;
@@ -82,7 +82,7 @@ sudo apt-get install httpie
 
 echo "Installing dependencies required for tests in codegens/dart"
 pushd ./codegens/dart-http &>/dev/null;
-  wget https://storage.googleapis.com/dart-archive/channels/stable/release/2.10.2/linux_packages/dart_2.10.2-1_amd64.deb
+  wget -q https://storage.googleapis.com/dart-archive/channels/stable/release/2.10.2/linux_packages/dart_2.10.2-1_amd64.deb
   sudo dpkg -i dart_2.10.2-1_amd64.deb
   echo '''name: test
 dependencies:
@@ -100,7 +100,7 @@ echo "Installing dependencies required for tests in codegens/php-guzzle"
 
 echo "Installing dependencies required for tests in codegens/r-rCurl and r-httr Installing R"
   sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9
-  sudo add-apt-repository 'deb [arch=amd64,i386] https://cran.rstudio.com/bin/linux/ubuntu xenial-cran40/'
+  sudo add-apt-repository 'deb https://cloud.r-project.org/bin/linux/ubuntu focal-cran40/'
   sudo apt-get update
   sudo apt-get install r-base
 
@@ -109,3 +109,22 @@ echo "Installing httr"
 
 echo "Installing RCurl"
 sudo R --vanilla -e 'install.packages("RCurl", version="1.98.1.6", repos="http://cran.us.r-project.org")'
+
+echo "Installing Rust"
+  sudo apt-get install -y build-essential pkg-config libssl-dev
+  curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
+  pushd ./codegens/rust-reqwest &>/dev/null;
+    echo '''[package]
+  name = "rust_reqwest_codegen"
+  version = "0.0.1"
+  edition = "2021"
+
+  [dependencies]
+  reqwest = { version = "0.11.14", features = ["json", "multipart"] }
+  tokio = { version = "1.26.0", features = ["full"] }
+  serde_json = { version = "1.0.94" }''' > Cargo.toml
+    mkdir src && echo '''fn main() {
+      println!("Hello, world!");
+    }''' > src/main.rs
+    cargo build
+  popd &>/dev/null;


Test patch:
diff --git a/codegens/curl/test/unit/fixtures/testcollection/collection.json b/codegens/curl/test/unit/fixtures/testcollection/collection.json
index 51cc05f..a7dba9b 100644
--- a/codegens/curl/test/unit/fixtures/testcollection/collection.json
+++ b/codegens/curl/test/unit/fixtures/testcollection/collection.json
@@ -1115,7 +1115,7 @@
 			"response": []
 		},
 		{
-			"name": "PROFIND request",
+			"name": "PROPFIND request",
 			"request": {
 				"method": "PROPFIND",
 				"header": [
diff --git a/codegens/golang/test/unit/fixtures/testcollection/collection.json b/codegens/golang/test/unit/fixtures/testcollection/collection.json
index ce70467..440714b 100644
--- a/codegens/golang/test/unit/fixtures/testcollection/collection.json
+++ b/codegens/golang/test/unit/fixtures/testcollection/collection.json
@@ -1114,7 +1114,7 @@
 			"response": []
 		},
 		{
-			"name": "PROFIND request",
+			"name": "PROPFIND request",
 			"request": {
 				"method": "PROPFIND",
 				"header": [
diff --git a/codegens/http/test/resources/test-collection.json b/codegens/http/test/resources/test-collection.json
index 2d49f05..018a375 100644
--- a/codegens/http/test/resources/test-collection.json
+++ b/codegens/http/test/resources/test-collection.json
@@ -1046,7 +1046,7 @@
 			"response": []
 		},
 		{
-			"name": "PROFIND request",
+			"name": "PROPFIND request",
 			"request": {
 				"method": "PROPFIND",
 				"header": [
diff --git a/codegens/js-jquery/test/unit/fixtures/sample_collection.json b/codegens/js-jquery/test/unit/fixtures/sample_collection.json
index bb25478..597792d 100644
--- a/codegens/js-jquery/test/unit/fixtures/sample_collection.json
+++ b/codegens/js-jquery/test/unit/fixtures/sample_collection.json
@@ -1240,7 +1240,7 @@
 			"response": []
 		},
 		{
-			"name": "PROFIND request",
+			"name": "PROPFIND request",
 			"request": {
 				"method": "PROPFIND",
 				"header": [
diff --git a/codegens/js-jquery/test/unit/fixtures/snippetFixtures.json b/codegens/js-jquery/test/unit/fixtures/snippetFixtures.json
index 70cc665..27181db 100644
--- a/codegens/js-jquery/test/unit/fixtures/snippetFixtures.json
+++ b/codegens/js-jquery/test/unit/fixtures/snippetFixtures.json
@@ -20,7 +20,7 @@
   "UNLINK request": "var%20settings%20%3D%20%7B%0A%20%20%20%20%22url%22%3A%20%22https%3A//mockbin.org/request%22%2C%0A%20%20%20%20%22method%22%3A%20%22UNLINK%22%2C%0A%20%20%20%20%22timeout%22%3A%20100%2C%0A%20%20%20%20%22headers%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%22Content-Type%22%3A%20%22text/plain%22%0A%20%20%20%20%7D%2C%0A%20%20%20%20%22data%22%3A%20%22Duis%20posuere%20augue%20vel%20cursus%20pharetra.%20In%20luctus%20a%20ex%20nec%20pretium.%20Praesent%20neque%20quam%2C%20tincidunt%20nec%20leo%20eget%2C%20rutrum%20vehicula%20magna.%5CnMaecenas%20consequat%20elementum%20elit%2C%20id%20semper%20sem%20tristique%20et.%20Integer%20pulvinar%20enim%20quis%20consectetur%20interdum%20volutpat.%22%2C%0A%7D%3B%0A%0A%24.ajax%28settings%29.done%28function%20%28response%29%20%7B%0A%20%20%20%20console.log%28response%29%3B%0A%7D%29%3B",
   "LOCK request": "var%20settings%20%3D%20%7B%0A%20%20%20%20%22url%22%3A%20%22https%3A//mockbin.org/request%22%2C%0A%20%20%20%20%22method%22%3A%20%22LOCK%22%2C%0A%20%20%20%20%22timeout%22%3A%20100%2C%0A%20%20%20%20%22headers%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%22Content-Type%22%3A%20%22text/plain%22%0A%20%20%20%20%7D%2C%0A%20%20%20%20%22data%22%3A%20%22Duis%20posuere%20augue%20vel%20cursus%20pharetra.%20In%20luctus%20a%20ex%20nec%20pretium.%20Praesent%20neque%20quam%2C%20tincidunt%20nec%20leo%20eget%2C%20rutrum%20vehicula%20magna.%5CnMaecenas%20consequat%20elementum%20elit%2C%20id%20semper%20sem%20tristique%20et.%20Integer%20pulvinar%20enim%20quis%20consectetur%20interdum%20volutpat.%22%2C%0A%7D%3B%0A%0A%24.ajax%28settings%29.done%28function%20%28response%29%20%7B%0A%20%20%20%20console.log%28response%29%3B%0A%7D%29%3B",
   "UNLOCK request": "var%20settings%20%3D%20%7B%0A%20%20%20%20%22url%22%3A%20%22https%3A//mockbin.org/request%22%2C%0A%20%20%20%20%22method%22%3A%20%22UNLOCK%22%2C%0A%20%20%20%20%22timeout%22%3A%20100%2C%0A%7D%3B%0A%0A%24.ajax%28settings%29.done%28function%20%28response%29%20%7B%0A%20%20%20%20console.log%28response%29%3B%0A%7D%29%3B",
-  "PROFIND request": "var%20settings%20%3D%20%7B%0A%20%20%20%20%22url%22%3A%20%22https%3A//mockbin.org/request%22%2C%0A%20%20%20%20%22method%22%3A%20%22PROPFIND%22%2C%0A%20%20%20%20%22timeout%22%3A%20100%2C%0A%20%20%20%20%22headers%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%22Content-Type%22%3A%20%22text/plain%22%0A%20%20%20%20%7D%2C%0A%20%20%20%20%22data%22%3A%20%22Duis%20posuere%20augue%20vel%20cursus%20pharetra.%20In%20luctus%20a%20ex%20nec%20pretium.%20Praesent%20neque%20quam%2C%20tincidunt%20nec%20leo%20eget%2C%20rutrum%20vehicula%20magna.%5CnMaecenas%20consequat%20elementum%20elit%2C%20id%20semper%20sem%20tristique%20et.%20Integer%20pulvinar%20enim%20quis%20consectetur%20interdum%20volutpat.%22%2C%0A%7D%3B%0A%0A%24.ajax%28settings%29.done%28function%20%28response%29%20%7B%0A%20%20%20%20console.log%28response%29%3B%0A%7D%29%3B",
+  "PROPFIND request": "var%20settings%20%3D%20%7B%0A%20%20%20%20%22url%22%3A%20%22https%3A//mockbin.org/request%22%2C%0A%20%20%20%20%22method%22%3A%20%22PROPFIND%22%2C%0A%20%20%20%20%22timeout%22%3A%20100%2C%0A%20%20%20%20%22headers%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%22Content-Type%22%3A%20%22text/plain%22%0A%20%20%20%20%7D%2C%0A%20%20%20%20%22data%22%3A%20%22Duis%20posuere%20augue%20vel%20cursus%20pharetra.%20In%20luctus%20a%20ex%20nec%20pretium.%20Praesent%20neque%20quam%2C%20tincidunt%20nec%20leo%20eget%2C%20rutrum%20vehicula%20magna.%5CnMaecenas%20consequat%20elementum%20elit%2C%20id%20semper%20sem%20tristique%20et.%20Integer%20pulvinar%20enim%20quis%20consectetur%20interdum%20volutpat.%22%2C%0A%7D%3B%0A%0A%24.ajax%28settings%29.done%28function%20%28response%29%20%7B%0A%20%20%20%20console.log%28response%29%3B%0A%7D%29%3B",
   "VIEW request": "var%20settings%20%3D%20%7B%0A%20%20%20%20%22url%22%3A%20%22https%3A//mockbin.org/request%22%2C%0A%20%20%20%20%22method%22%3A%20%22VIEW%22%2C%0A%20%20%20%20%22timeout%22%3A%20100%2C%0A%20%20%20%20%22headers%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%22Content-Type%22%3A%20%22text/plain%22%0A%20%20%20%20%7D%2C%0A%20%20%20%20%22data%22%3A%20%22Duis%20posuere%20augue%20vel%20cursus%20pharetra.%20In%20luctus%20a%20ex%20nec%20pretium.%20Praesent%20neque%20quam%2C%20tincidunt%20nec%20leo%20eget%2C%20rutrum%20vehicula%20magna.%5CnMaecenas%20consequat%20elementum%20elit%2C%20id%20semper%20sem%20tristique%20et.%20Integer%20pulvinar%20enim%20quis%20consectetur%20interdum%20volutpat.%22%2C%0A%7D%3B%0A%0A%24.ajax%28settings%29.done%28function%20%28response%29%20%7B%0A%20%20%20%20console.log%28response%29%3B%0A%7D%29%3B",
   "PURGE Request": "var%20settings%20%3D%20%7B%0A%20%20%20%20%22url%22%3A%20%22https%3A//9c76407d-5b8d-4b22-99fb-8c47a85d9848.mock.pstmn.io%22%2C%0A%20%20%20%20%22method%22%3A%20%22PURGE%22%2C%0A%20%20%20%20%22timeout%22%3A%20100%2C%0A%7D%3B%0A%0A%24.ajax%28settings%29.done%28function%20%28response%29%20%7B%0A%20%20%20%20console.log%28response%29%3B%0A%7D%29%3B",
   "COPY Request": "var%20settings%20%3D%20%7B%0A%20%20%20%20%22url%22%3A%20%22https%3A//9c76407d-5b8d-4b22-99fb-8c47a85d9848.mock.pstmn.io%22%2C%0A%20%20%20%20%22method%22%3A%20%22COPY%22%2C%0A%20%20%20%20%22timeout%22%3A%20100%2C%0A%7D%3B%0A%0A%24.ajax%28settings%29.done%28function%20%28response%29%20%7B%0A%20%20%20%20console.log%28response%29%3B%0A%7D%29%3B"
diff --git a/codegens/js-xhr/test/unit/fixtures/testcollection/collection.json b/codegens/js-xhr/test/unit/fixtures/testcollection/collection.json
index 8afd061..e1d75d1 100644
--- a/codegens/js-xhr/test/unit/fixtures/testcollection/collection.json
+++ b/codegens/js-xhr/test/unit/fixtures/testcollection/collection.json
@@ -1055,7 +1055,7 @@
 			"response": []
 		},
 		{
-			"name": "PROFIND request",
+			"name": "PROPFIND request",
 			"request": {
 				"method": "PROPFIND",
 				"header": [
diff --git a/codegens/kotlin-okhttp/test/unit/validation.test.js b/codegens/kotlin-okhttp/test/unit/validation.test.js
index 3291640..c1b4ff5 100644
--- a/codegens/kotlin-okhttp/test/unit/validation.test.js
+++ b/codegens/kotlin-okhttp/test/unit/validation.test.js
@@ -11,7 +11,7 @@ describe('package.json', function () {
     expect(package.com_postman_plugin.type).to.equal('code_generator');
     expect(package.com_postman_plugin.lang).to.be.a('string');
     expect(package.com_postman_plugin.variant).to.be.a('string');
-    expect(package.com_postman_plugin.syntax_mode).to.be.equal('java');
+    expect(package.com_postman_plugin.syntax_mode).to.be.equal('kotlin');
   });
   it('should have main property with relative path to object with convert property', function () {
     var languageModule;
diff --git a/codegens/libcurl/test/unit/fixtures/testcollection/collection.json b/codegens/libcurl/test/unit/fixtures/testcollection/collection.json
index 70b321c..a9581bb 100644
--- a/codegens/libcurl/test/unit/fixtures/testcollection/collection.json
+++ b/codegens/libcurl/test/unit/fixtures/testcollection/collection.json
@@ -1141,7 +1141,7 @@
 			"response": []
 		},
 		{
-			"name": "PROFIND request",
+			"name": "PROPFIND request",
 			"request": {
 				"method": "PROPFIND",
 				"header": [
diff --git a/codegens/nodejs-native/test/unit/fixtures/testcollection/collection.json b/codegens/nodejs-native/test/unit/fixtures/testcollection/collection.json
index 501fce4..a28abdb 100644
--- a/codegens/nodejs-native/test/unit/fixtures/testcollection/collection.json
+++ b/codegens/nodejs-native/test/unit/fixtures/testcollection/collection.json
@@ -1176,7 +1176,7 @@
       "response": []
     },
     {
-      "name": "PROFIND request",
+      "name": "PROPFIND request",
       "request": {
         "method": "PROPFIND",
         "header": [
diff --git a/codegens/php-guzzle/test/unit/fixtures/sample_collection.json b/codegens/php-guzzle/test/unit/fixtures/sample_collection.json
index 5262fcc..cb4623e 100644
--- a/codegens/php-guzzle/test/unit/fixtures/sample_collection.json
+++ b/codegens/php-guzzle/test/unit/fixtures/sample_collection.json
@@ -1236,7 +1236,7 @@
 			"response": []
 		},
 		{
-			"name": "PROFIND request",
+			"name": "PROPFIND request",
 			"request": {
 				"method": "PROPFIND",
 				"header": [
diff --git a/codegens/php-pecl-http/test/unit/fixtures/sample_collection.json b/codegens/php-pecl-http/test/unit/fixtures/sample_collection.json
index f172782..33cd441 100644
--- a/codegens/php-pecl-http/test/unit/fixtures/sample_collection.json
+++ b/codegens/php-pecl-http/test/unit/fixtures/sample_collection.json
@@ -1236,7 +1236,7 @@
 			"response": []
 		},
 		{
-			"name": "PROFIND request",
+			"name": "PROPFIND request",
 			"request": {
 				"method": "PROPFIND",
 				"header": [
diff --git a/codegens/r-httr/test/unit/fixtures/sample_collection.json b/codegens/r-httr/test/unit/fixtures/sample_collection.json
index ad659b1..c8347f9 100644
--- a/codegens/r-httr/test/unit/fixtures/sample_collection.json
+++ b/codegens/r-httr/test/unit/fixtures/sample_collection.json
@@ -1238,7 +1238,7 @@
 			"response": []
 		},
 		{
-			"name": "PROFIND request",
+			"name": "PROPFIND request",
 			"request": {
 				"method": "PROPFIND",
 				"header": [
diff --git a/codegens/r-rcurl/test/unit/fixtures/sample_collection.json b/codegens/r-rcurl/test/unit/fixtures/sample_collection.json
index 0b15e39..817dd0e 100644
--- a/codegens/r-rcurl/test/unit/fixtures/sample_collection.json
+++ b/codegens/r-rcurl/test/unit/fixtures/sample_collection.json
@@ -1236,7 +1236,7 @@
 			"response": []
 		},
 		{
-			"name": "PROFIND request",
+			"name": "PROPFIND request",
 			"request": {
 				"method": "PROPFIND",
 				"header": [
diff --git a/codegens/rust-reqwest/npm/test-lint.js b/codegens/rust-reqwest/npm/test-lint.js
new file mode 100644
index 0000000..2f4db0c
--- /dev/null
+++ b/codegens/rust-reqwest/npm/test-lint.js
@@ -0,0 +1,56 @@
+#!/usr/bin/env node
+var shell = require('shelljs'),
+  chalk = require('chalk'),
+  async = require('async'),
+  ESLintCLIEngine = require('eslint').CLIEngine,
+
+  /**
+     * The list of source code files / directories to be linted.
+     *
+     * @type {Array}
+     */
+  LINT_SOURCE_DIRS = [
+    './lib',
+    './test',
+    './npm/*.js',
+    './index.js'
+  ];
+
+module.exports = function (exit) {
+  // banner line
+  console.info(chalk.yellow.bold('\nLinting files using eslint...'));
+
+  async.waterfall([
+
+    /**
+         * Instantiates an ESLint CLI engine and runs it in the scope defined within LINT_SOURCE_DIRS.
+         *
+         * @param {Function} next - The callback function whose invocation marks the end of the lint test run.
+         * @returns {*}
+         */
+    function (next) {
+      next(null, (new ESLintCLIEngine()).executeOnFiles(LINT_SOURCE_DIRS));
+    },
+
+    /**
+         * Processes a test report from the Lint test runner, and displays meaningful results.
+         *
+         * @param {Object} report - The overall test report for the current lint test.
+         * @param {Object} report.results - The set of test results for the current lint run.
+         * @param {Function} next - The callback whose invocation marks the completion of the post run tasks.
+         * @returns {*}
+         */
+    function (report, next) {
+      var errorReport = ESLintCLIEngine.getErrorResults(report.results);
+      // log the result to CLI
+      console.info(ESLintCLIEngine.getFormatter()(report.results));
+      // log the success of the parser if it has no errors
+      (errorReport && !errorReport.length) && console.info(chalk.green('eslint ok!'));
+      // ensure that the exit code is non zero in case there was an error
+      next(Number(errorReport && errorReport.length) || 0);
+    }
+  ], exit);
+};
+
+// ensure we run this script exports if this is a direct stdin.tty run
+!module.parent && module.exports(shell.exit);
diff --git a/codegens/rust-reqwest/npm/test-newman.js b/codegens/rust-reqwest/npm/test-newman.js
new file mode 100644
index 0000000..ae7d2af
--- /dev/null
+++ b/codegens/rust-reqwest/npm/test-newman.js
@@ -0,0 +1,59 @@
+#!/usr/bin/env node
+/* eslint-env node, es6 */
+// ---------------------------------------------------------------------------------------------------------------------
+// This script is intended to execute all newman tests.
+// ---------------------------------------------------------------------------------------------------------------------
+
+var shell = require('shelljs'),
+
+  // set directories and files for test and coverage report
+  path = require('path'),
+
+  NYC = require('nyc'),
+  chalk = require('chalk'),
+  recursive = require('recursive-readdir'),
+
+  COV_REPORT_PATH = '.coverage',
+  SPEC_SOURCE_DIR = path.join(__dirname, '..', 'test', 'newman');
+
+module.exports = function (exit) {
+  // banner line
+  console.info(chalk.yellow.bold('Running newman tests using mocha on node...'));
+
+  shell.test('-d', COV_REPORT_PATH) && shell.rm('-rf', COV_REPORT_PATH);
+  shell.mkdir('-p', COV_REPORT_PATH);
+
+  var Mocha = require('mocha'),
+    nyc = new NYC({
+      reportDir: COV_REPORT_PATH,
+      tempDirectory: COV_REPORT_PATH,
+      reporter: ['text', 'lcov', 'text-summary'],
+      exclude: ['config', 'test'],
+      hookRunInContext: true,
+      hookRunInThisContext: true
+    });
+
+  nyc.wrap();
+  // add all spec files to mocha
+  recursive(SPEC_SOURCE_DIR, function (err, files) {
+    if (err) { console.error(err); return exit(1); }
+
+    var mocha = new Mocha({ timeout: 1000 * 60 });
+
+    files.filter(function (file) { // extract all test files
+      return (file.substr(-8) === '.test.js');
+    }).forEach(mocha.addFile.bind(mocha));
+
+    mocha.run(function (runError) {
+      runError && console.error(runError.stack || runError);
+
+      nyc.reset();
+      nyc.writeCoverageFile();
+      nyc.report();
+      exit(runError ? 1 : 0);
+    });
+  });
+};
+
+// ensure we run this script exports if this is a direct stdin.tty run
+!module.parent && module.exports(shell.exit);
diff --git a/codegens/rust-reqwest/npm/test-unit.js b/codegens/rust-reqwest/npm/test-unit.js
new file mode 100755
index 0000000..0de7fd5
--- /dev/null
+++ b/codegens/rust-reqwest/npm/test-unit.js
@@ -0,0 +1,59 @@
+#!/usr/bin/env node
+/* eslint-env node, es6 */
+// ---------------------------------------------------------------------------------------------------------------------
+// This script is intended to execute all unit tests.
+// ---------------------------------------------------------------------------------------------------------------------
+
+var shell = require('shelljs'),
+
+  // set directories and files for test and coverage report
+  path = require('path'),
+
+  NYC = require('nyc'),
+  chalk = require('chalk'),
+  recursive = require('recursive-readdir'),
+
+  COV_REPORT_PATH = '.coverage',
+  SPEC_SOURCE_DIR = path.join(__dirname, '..', 'test', 'unit');
+
+module.exports = function (exit) {
+  // banner line
+  console.info(chalk.yellow.bold('Running unit tests using mocha on node...'));
+
+  shell.test('-d', COV_REPORT_PATH) && shell.rm('-rf', COV_REPORT_PATH);
+  shell.mkdir('-p', COV_REPORT_PATH);
+
+  var Mocha = require('mocha'),
+    nyc = new NYC({
+      reportDir: COV_REPORT_PATH,
+      tempDirectory: COV_REPORT_PATH,
+      reporter: ['text', 'lcov', 'text-summary'],
+      exclude: ['config', 'test'],
+      hookRunInContext: true,
+      hookRunInThisContext: true
+    });
+
+  nyc.wrap();
+  // add all spec files to mocha
+  recursive(SPEC_SOURCE_DIR, function (err, files) {
+    if (err) { console.error(err); return exit(1); }
+
+    var mocha = new Mocha({ timeout: 1000 * 60 });
+
+    files.filter(function (file) { // extract all test files
+      return (file.substr(-8) === '.test.js');
+    }).forEach(mocha.addFile.bind(mocha));
+
+    mocha.run(function (runError) {
+      runError && console.error(runError.stack || runError);
+
+      nyc.reset();
+      nyc.writeCoverageFile();
+      nyc.report();
+      exit(runError ? 1 : 0);
+    });
+  });
+};
+
+// ensure we run this script exports if this is a direct stdin.tty run
+!module.parent && module.exports(shell.exit);
diff --git a/codegens/rust-reqwest/npm/test.js b/codegens/rust-reqwest/npm/test.js
new file mode 100755
index 0000000..b20d0f3
--- /dev/null
+++ b/codegens/rust-reqwest/npm/test.js
@@ -0,0 +1,16 @@
+#!/usr/bin/env node
+var chalk = require('chalk'),
+  exit = require('shelljs').exit,
+  prettyms = require('pretty-ms'),
+  startedAt = Date.now(),
+  name = require('../package.json').name;
+
+require('async').series([
+  require('./test-lint'),
+  require('./test-newman'),
+  require('./test-unit')
+], function (code) {
+  // eslint-disable-next-line max-len
+  console.info(chalk[code ? 'red' : 'green'](`\n${name}: duration ${prettyms(Date.now() - startedAt)}\n${name}: ${code ? 'not ok' : 'ok'}!`));
+  exit(code && (typeof code === 'number' ? code : 1) || 0);
+});
diff --git a/codegens/rust-reqwest/test/.eslintrc b/codegens/rust-reqwest/test/.eslintrc
new file mode 100644
index 0000000..9d477e3
--- /dev/null
+++ b/codegens/rust-reqwest/test/.eslintrc
@@ -0,0 +1,30 @@
+{
+  "plugins": [
+    "mocha"
+  ],
+  "env": {
+    "mocha": true,
+    "node": true,
+    "es6": true
+  },
+  "rules": {
+    // Mocha
+    "mocha/handle-done-callback": "error",
+    "mocha/max-top-level-suites": "error",
+    "mocha/no-exclusive-tests": "error",
+    "mocha/no-global-tests": "error",
+    "mocha/no-hooks-for-single-case": "off",
+    "mocha/no-hooks": "off",
+    "mocha/no-identical-title": "error",
+    "mocha/no-mocha-arrows": "error",
+    "mocha/no-nested-tests": "error",
+    "mocha/no-pending-tests": "error",
+    "mocha/no-return-and-callback": "error",
+    "mocha/no-sibling-hooks": "error",
+    "mocha/no-skipped-tests": "warn",
+    "mocha/no-synchronous-tests": "off",
+    "mocha/no-top-level-hooks": "warn",
+    "mocha/valid-test-description": "off",
+    "mocha/valid-suite-description": "off"
+  }
+}
diff --git a/codegens/rust-reqwest/test/newman/newman.test.js b/codegens/rust-reqwest/test/newman/newman.test.js
new file mode 100644
index 0000000..377f97b
--- /dev/null
+++ b/codegens/rust-reqwest/test/newman/newman.test.js
@@ -0,0 +1,15 @@
+const runNewmanTest = require('../../../../test/codegen/newman/newmanTestUtil').runNewmanTest,
+  convert = require('../../index').convert;
+
+describe('Convert for different types of request', function () {
+  const testConfig = {
+      runScript: 'cargo run -q',
+      compileScript: null,
+      fileName: 'src/main.rs'
+    },
+    options = {
+      indentCount: 4,
+      indentType: 'Space'
+    };
+  runNewmanTest(convert, options, testConfig);
+});
diff --git a/codegens/rust-reqwest/test/unit/convert.test.js b/codegens/rust-reqwest/test/unit/convert.test.js
new file mode 100644
index 0000000..45a4be4
--- /dev/null
+++ b/codegens/rust-reqwest/test/unit/convert.test.js
@@ -0,0 +1,120 @@
+const expect = require('chai').expect,
+  sdk = require('postman-collection'),
+  convert = require('../../lib/index').convert;
+
+describe('Rust reqwest converter', function () {
+  it('should throw an error when callback is not function', function () {
+    expect(function () { convert({}, {}); })
+      .to.throw('Rust~reqwest-convert: Callback is not a function');
+  });
+
+  it('should set no redirect policy when followRedirect is set to false', function () {
+    const request = new sdk.Request({
+        'method': 'GET',
+        'header': [],
+        'url': {
+          'raw': 'http://postman-echo.com/get',
+          'protocol': 'http',
+          'host': [
+            'postman-echo',
+            'com'
+          ],
+          'path': [
+            'get'
+          ]
+        }
+      }),
+      options = {followRedirect: false};
+    convert(request, options, function (error, snippet) {
+      if (error) {
+        expect.fail(null, null, error);
+      }
+      expect(snippet).to.be.a('string');
+      expect(snippet).to.include('redirect(reqwest::redirect::Policy::none())');
+    });
+  });
+
+  it('should set read timeout when requestTimeout is set to non zero value', function () {
+    const request = new sdk.Request({
+        'method': 'GET',
+        'header': [],
+        'url': {
+          'raw': 'http://postman-echo.com/get',
+          'protocol': 'http',
+          'host': [
+            'postman-echo',
+            'com'
+          ],
+          'path': [
+            'get'
+          ]
+        }
+      }),
+      options = {requestTimeout: 3000};
+    convert(request, options, function (error, snippet) {
+      if (error) {
+        expect.fail(null, null, error);
+      }
+      expect(snippet).to.be.a('string');
+      expect(snippet).to.include('timeout(std::time::Duration::from_millis(3000))');
+    });
+  });
+
+  it('should use the method name directly if it is part of allowed methods', function () {
+    ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'CONNECT', 'PATH', 'TRACE'].forEach(function (method) {
+      const request = new sdk.Request({
+          'method': method,
+          'header': [],
+          'url': {
+            'raw': 'http://postman-echo.com/get',
+            'protocol': 'http',
+            'host': [
+              'postman-echo',
+              'com'
+            ],
+            'path': [
+              'get'
+            ]
+          }
+        }),
+        options = {};
+      convert(request, options, function (error, snippet) {
+        if (error) {
+          expect.fail(null, null, error);
+        }
+        expect(snippet).to.be.a('string');
+        expect(snippet).to.include(`let request = client.request(reqwest::Method::${method}`);
+      });
+    });
+  });
+
+  it('should use the method name using bytes if it not part of the allowed list', function () {
+    ['PROPFIND', 'PURGE', 'LOCK', 'UNLOCK', 'LINK', 'UNLINK', 'COPY'].forEach(function (method) {
+      const request = new sdk.Request({
+          'method': method,
+          'header': [],
+          'url': {
+            'raw': 'http://postman-echo.com/get',
+            'protocol': 'http',
+            'host': [
+              'postman-echo',
+              'com'
+            ],
+            'path': [
+              'get'
+            ]
+          }
+        }),
+        options = {};
+      convert(request, options, function (error, snippet) {
+        if (error) {
+          expect.fail(null, null, error);
+        }
+        expect(snippet).to.be.a('string');
+        expect(snippet).to.include(`let method = "${method}"`);
+        expect(snippet).to.include('let request = client.request(reqwest::Method::from_bytes(method.as_bytes())?');
+      });
+    });
+  });
+
+});
diff --git a/codegens/rust-reqwest/test/unit/validation.test.js b/codegens/rust-reqwest/test/unit/validation.test.js
new file mode 100644
index 0000000..15782be
--- /dev/null
+++ b/codegens/rust-reqwest/test/unit/validation.test.js
@@ -0,0 +1,30 @@
+var expect = require('chai').expect,
+  path = require('path'),
+
+  package = require(path.resolve('.', 'package.json'));
+
+
+describe('package.json', function () {
+  it('should have com_postman_plugin object with valid properties', function () {
+    expect(package).to.have.property('com_postman_plugin');
+
+    expect(package.com_postman_plugin.type).to.equal('code_generator');
+    expect(package.com_postman_plugin.lang).to.be.a('string');
+    expect(package.com_postman_plugin.variant).to.be.a('string');
+    expect(package.com_postman_plugin.syntax_mode).to.be.equal('rust');
+  });
+  it('should have main property with relative path to object with convert property', function () {
+    var languageModule;
+
+    expect(package.main).to.be.a('string');
+
+    try {
+      languageModule = require(path.resolve('.', package.main));
+    }
+    catch (error) {
+      console.error(error);
+    }
+    expect(languageModule).to.be.a('object');
+    expect(languageModule.convert).to.be.a('function');
+  });
+});
diff --git a/codegens/shell-httpie/examples/test-collection.json b/codegens/shell-httpie/examples/test-collection.json
index b51bcfe..5215ca5 100644
--- a/codegens/shell-httpie/examples/test-collection.json
+++ b/codegens/shell-httpie/examples/test-collection.json
@@ -1108,7 +1108,7 @@
 			"response": []
 		},
 		{
-			"name": "PROFIND request",
+			"name": "PROPFIND request",
 			"request": {
 				"method": "PROPFIND",
 				"header": [
diff --git a/codegens/swift/test/unit/fixtures/testcollection/collection.json b/codegens/swift/test/unit/fixtures/testcollection/collection.json
index ba436e2..dff2cd8 100644
--- a/codegens/swift/test/unit/fixtures/testcollection/collection.json
+++ b/codegens/swift/test/unit/fixtures/testcollection/collection.json
@@ -1108,7 +1108,7 @@
 			"response": []
 		},
 		{
-			"name": "PROFIND request",
+			"name": "PROPFIND request",
 			"request": {
 				"method": "PROPFIND",
 				"header": [
diff --git a/test/codegen/newman/fixtures/basicCollection.json b/test/codegen/newman/fixtures/basicCollection.json
index db245f7..d0c79ac 100644
--- a/test/codegen/newman/fixtures/basicCollection.json
+++ b/test/codegen/newman/fixtures/basicCollection.json
@@ -330,7 +330,7 @@
 				],
 				"body": {
 					"mode": "raw",
-					"raw": "var val = 6;\nconsole.log(val);console.log('text\r\n');\n"
+					"raw": "var val = 6;\nconsole.log(val);console.log('$text\r\n');\n"
 				},
 				"url": {
 					"raw": "https://postman-echo.com/post",
@@ -685,4 +685,4 @@
 			}
 		}
 	]
-}
\ No newline at end of file
+}
diff --git a/test/codegen/newman/fixtures/unsupportedMethods.json b/test/codegen/newman/fixtures/unsupportedMethods.json
index 1b88db2..2ac6761 100644
--- a/test/codegen/newman/fixtures/unsupportedMethods.json
+++ b/test/codegen/newman/fixtures/unsupportedMethods.json
@@ -115,7 +115,7 @@
 			"response": []
 		},
 		{
-			"name": "PROFIND request",
+			"name": "PROPFIND request",
 			"request": {
 				"method": "PROPFIND",
 				"header": [