Mashape/httpsnippet

View on GitHub
src/targets/rust/reqwest/client.ts

Summary

Maintainability
C
7 hrs
Test Coverage
/**
 * @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 { CodeBuilder } from '../../../helpers/code-builder';
import { Client } from '../../targets';
import { literalRepresentation } from '../helpers';

export const reqwest: Client = {
  info: {
    key: 'reqwest',
    title: 'reqwest',
    link: 'https://docs.rs/reqwest/latest/reqwest/',
    description: 'reqwest HTTP library',
  },
  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<string, any> = {};
    const files: Record<string, string> = {};

    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) {
          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::<serde_json::Value>()', 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()`,
  `}`,
];