Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "2.0.0",
"version": "2.1.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't need to do this in a PR, when they publish a release they'll handle this.

"name": "httpsnippet",
"description": "HTTP Request snippet generator for *most* languages",
"author": "Ahmad Nassri <[email protected]> (https://www.mashape.com/)",
Expand Down
1 change: 1 addition & 0 deletions src/targets/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = {
python: require('./python'),
r: require('./r'),
ruby: require('./ruby'),
rust: require('./rust'),
shell: require('./shell'),
swift: require('./swift')
}
45 changes: 45 additions & 0 deletions src/targets/rust/code-builder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const OriginalCodeBuilder = require('../../helpers/code-builder')
class CodeBuilder extends OriginalCodeBuilder {
constructor(indent = ' ', join = '\n', defaultLevel = 0) {
super(indent, join)
this.indentLevel = defaultLevel
}

/** Current indentation level */
indentLevel = 0

/**
* Increase indentation level
*
* @returns {this}
*/
indent() {
this.indentLevel++
return this
}
/**
* Decrease indentation level
*
* @returns {this}
*/
unindent() {
this.indentLevel--
return this
}
/**
* Reset indentation level
*
* @returns {this}
*/
reindent() {
this.indentLevel = 0
return this
}

/** @inheritdoc */
push(str) {
return super.push(this.indentLevel, str)
}
}

module.exports = CodeBuilder
12 changes: 12 additions & 0 deletions src/targets/rust/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict'

module.exports = {
info: {
key: 'rust',
title: 'Rust',
extname: '.rs',
default: 'reqwest'
},

reqwest: require('./reqwest'),
}
47 changes: 47 additions & 0 deletions src/targets/rust/infer-features.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* @typedef {Object} Features
* @property {bool} fullMethod Whether the request method should be written in full form
* @property {bool} inlineMethod Whether the request method can be written in inline form
* @property {bool} headers Whether the request has custom headers
* @property {bool} query Whether the request has custom url query
* @property {bool} raw Whether the request has post data of any type
* @property {bool} form Whether the request has post data of type form
* @property {bool} json Whether the request has post data of type json
* @property {bool} body Whether the request has post data of other type
*/

/** Methods that can be inlined, e.g. `client.get(...)` */
const inlineMethods = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH']
/** Methods that can not be inlined, so we should do this: client.request(Method::OPTIONS, ...) */
const fullMethods = ['OPTIONS', 'CONNECT', 'TRACE']

/**
* @param {any} data
* @returns {bool} If the value is truthy, and in case of object, has at least 1 key
*/
function isSet(data) {
if (!data) return false
if (typeof data !== 'object') return !!data
return Object.keys(data).length > 0
}

/**
*
* @param {object} source HAR object processed by the library
* @param {Options} options Options passed to the generator function
* @returns {Features}
*/
function inferFeatures(source, options) {
const fullMethod = fullMethods.includes(source.method)
const inlineMethod = inlineMethods.includes(source.method)
const headers = isSet(source.queryObj)
const query = options.expandQuery && isSet(source.queryObj)
const raw = isSet(source.postData.text)
const form = options.expandBody && isSet(source.postData.paramsObj)
const json = options.expandBody && isSet(source.postData.jsonObj)
const body = !form && !json && raw

return { fullMethod, inlineMethod, headers, query, raw, form, json, body }
}

module.exports = inferFeatures
131 changes: 131 additions & 0 deletions src/targets/rust/reqwest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* @description
* HTTP code snippet generator for Rust using Reqwest
*
* @author
* @eeWynell
*
* for any questions or issues regarding the generated code snippet, please open an issue mentioning the author.
*/

/**
* @typedef {Object} Options
* @property {bool} blocking Whether the request should be blocking
* @property {bool} boilerplate Whether to show boilerplate code
* @property {bool} print Whether to print the response
* @property {bool} expandQuery Whether to expand the url query
* @property {bool} expandBody Whether to expand the body
*/

'use strict'

const str = require('./str')
const inferFeatures = require('./infer-features')
const CodeBuilder = require('./code-builder')

/**
* @param {object} source
* @param {Options} options
* @returns {string} Code
*/
module.exports = function (source, options) {
options = Object.assign(
{
blocking: true,
boilerplate: true,
print: true,
expandQuery: false,
expandBody: true,
},
options
)

const features = inferFeatures(source, options)
const code = new CodeBuilder()

if (options.boilerplate) {
if (options.blocking) code.push('use reqwest::blocking::Client;')
else code.push('use tokio;').push('use reqwest::Client;')

if (features.fullMethod) code.push('reqwest::Client')
if (features.headers) code.push('use reqwest::header::HeaderMap;')
if (features.json) code.push('use serde_json::json;')
if (features.query || features.form) code.push('use maplit::hashmap;')

code.blank()
if (options.blocking) code.push('fn main() {')
else code.push('#[tokio::main]').push('async fn main() {')
code.indent()
}

code.push('let client = Client::new();').blank()

if (features.headers) {
code.push('let mut headers = HeaderMap::new();')
for (const [name, value] of Object.entries(source.headersObj)) {
code.push(str`headers.insert("${name}", "${value}".parse().unwrap());`)
}
code.blank()
}

if (features.query) {
code.push('let query = hashmap!{').indent()
for (const [name, value] of Object.entries(source.queryObj)) {
code.push(str`"${name}" => vec!${[].concat(value)},`)
}
code.unindent().push('};').blank()
}

if (features.form) {
code.push('let form = hashmap!{').indent()
for (const [name, value] of Object.entries(source.postData.paramsObj)) {
code.push(str`"${name}" => vec!${[].concat(value)},`)
}
code.unindent().push('};').blank()
}

if (features.json) {
code.push(str`let json = json!("${source.postData.jsonObj}");`).blank()
}

if (features.body) {
code.push(str`let body = "${source.postData.text}";`).blank()
}

code.push('let resp = client').indent()

const method = source.method
const url = options.expandQuery ? source.url : source.fullUrl

if (features.inlineMethod) code.push(`.${method.toLowerCase()}("${url}")`)
else if (features.fullMethod) code.push(`.request(Method::${method})`)
else code.push(`.post("${url}") //! Can't use method "${method}"`)

if (features.headers) code.push('.headers(headers)')
if (features.query) code.push('.query(&query)')
if (features.form) code.push('.form(&form)')
if (features.json) code.push('.json(&json)')
if (features.body) code.push('.body(body)')

if (options.blocking) code.push('.send()').push('.unwrap();')
else code.push('.send()').push('.await').push('.unwrap();')

code.unindent()

if (options.print) {
code.blank().push('println!("{:?}", resp);')
}

if (options.boilerplate) {
code.unindent().push('}').blank()
}

return code.join()
}

module.exports.info = {
key: 'reqwest',
title: 'Reqwest',
link: 'https://docs.rs/reqwest/',
description: 'HTTP Request using Reqwest library for Rust',
}
49 changes: 49 additions & 0 deletions src/targets/rust/str.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Turns data into a string, without adding quotes
*
* @param {any} data Any data to be stringified
* @returns {string}
*/
function stringify(data) {
if (typeof data === 'object') return JSON.stringify(data)
return String(data)
}

/**
* Escapes characters in the given string
*
* @param {string} data String to escape
* @returns {string}
*/
function escape(data) {
return JSON.stringify(data).slice(1, -1)
}

/**
* Looks like a reason to use a ES6 tag function first time in my life
* Also if you replace `parts` with `parts.raw`, you will avoid escaping characters in the template
*
* @example
* let test = { "foo": "bar" }
* str`unquoted ${test}, quoted "${test}"`
* // unquoted {"foo":"bar"}, quoted "{\"foo\":\"bar\"}"
*
* @param {string[]} parts Template literal string parts
* @param {...string} args Template literal expressions
*
* @returns {string} The same string but expressions are JSON-stringified
*/
function str(parts, ...args) {
let [res, ...rest] = parts
for (i = 0; i < rest.length; i++) {
let expr = stringify(args[i])
let nextTmpl = rest[i]
if (res.endsWith('"') && nextTmpl.startsWith('"')) {
expr = escape(expr)
}
res += expr + nextTmpl
}
return res
}

module.exports = str
3 changes: 3 additions & 0 deletions test/targets/rust/reqwest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict'

module.exports = function (snippet, fixtures) {}