From 5b96fb5a3a7b78fde21389367f7b80ec39ad90b1 Mon Sep 17 00:00:00 2001 From: benfaerber Date: Wed, 1 Jan 2025 10:15:24 -0500 Subject: [PATCH] Add REST format importer (for JetBrains/VSCode) --- CHANGELOG.md | 4 + Cargo.lock | 15 + crates/cli/src/commands/import.rs | 10 +- crates/import/Cargo.toml | 1 + crates/import/src/lib.rs | 2 + crates/import/src/rest.rs | 648 ++++++++++++++++++++++++++++++ docs/src/cli/import.md | 6 +- test_data/rest_http_bin.http | 41 ++ test_data/rest_imported.yml | 63 +++ test_data/rest_pets.json | 4 + 10 files changed, 790 insertions(+), 4 deletions(-) create mode 100644 crates/import/src/rest.rs create mode 100644 test_data/rest_http_bin.http create mode 100644 test_data/rest_imported.yml create mode 100644 test_data/rest_pets.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dd8ffe9..8bc16ed4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ## [Unreleased] - ReleaseDate +### Added + +- Add REST Importer for VSCode and Jetbrains + ### Changed - Update [editor-command](https://crates.io/crates/editor-command), which replaces [shellish_parse](https://crates.io/crates/shellish_parse) with [shell-words](https://crates.io/crates/shell-words) for editor and pager command parsing diff --git a/Cargo.lock b/Cargo.lock index 65ef829a..c608f145 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1946,6 +1946,20 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "rest_parser" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "575c220d2c01ec29da1d36c6fbd1deb7fe001597336f54f22f5f1a35cbeaa5c8" +dependencies = [ + "anyhow", + "base64", + "httparse", + "indexmap", + "nom", + "url", +] + [[package]] name = "ring" version = "0.17.8" @@ -2495,6 +2509,7 @@ dependencies = [ "openapiv3", "pretty_assertions", "reqwest", + "rest_parser", "rstest", "serde", "serde_json", diff --git a/crates/cli/src/commands/import.rs b/crates/cli/src/commands/import.rs index 4aeb63a9..d39cb97c 100644 --- a/crates/cli/src/commands/import.rs +++ b/crates/cli/src/commands/import.rs @@ -9,6 +9,9 @@ use std::{ }; /// Generate a Slumber request collection from an external format +/// +/// See docs for more info on formats: +/// https://slumber.lucaspickering.me/book/cli/import.html #[derive(Clone, Debug, Parser)] pub struct ImportCommand { /// Input format @@ -25,8 +28,12 @@ enum Format { /// Insomnia export format (JSON or YAML) Insomnia, /// OpenAPI v3.0 (JSON or YAML) v3.1 not supported but may work - /// https://spec.openapis.org/oas/v3.0.3 Openapi, + /// VSCode `.rest` or JetBrains `.http` format [aliases: vscode, jetbrains] + // Use visible_alias (and remove from doc comment) after + // https://github.com/clap-rs/clap/pull/5480 + #[value(alias = "vscode", alias = "jetbrains")] + Rest, } impl Subcommand for ImportCommand { @@ -37,6 +44,7 @@ impl Subcommand for ImportCommand { slumber_import::from_insomnia(&self.input_file)? } Format::Openapi => slumber_import::from_openapi(&self.input_file)?, + Format::Rest => slumber_import::from_rest(&self.input_file)?, }; // Write the output diff --git a/crates/import/Cargo.toml b/crates/import/Cargo.toml index 889661c0..7fd2e6a8 100644 --- a/crates/import/Cargo.toml +++ b/crates/import/Cargo.toml @@ -16,6 +16,7 @@ indexmap = {workspace = true, features = ["serde"]} itertools = {workspace = true} mime = {workspace = true} openapiv3 = "2.0.0" +rest_parser = "0.1.6" reqwest = {workspace = true} serde = {workspace = true} serde_json = {workspace = true} diff --git a/crates/import/src/lib.rs b/crates/import/src/lib.rs index cd629fd0..cc4e072b 100644 --- a/crates/import/src/lib.rs +++ b/crates/import/src/lib.rs @@ -1,5 +1,7 @@ mod insomnia; mod openapi; +mod rest; pub use insomnia::from_insomnia; pub use openapi::from_openapi; +pub use rest::from_rest; diff --git a/crates/import/src/rest.rs b/crates/import/src/rest.rs new file mode 100644 index 00000000..e1c82a86 --- /dev/null +++ b/crates/import/src/rest.rs @@ -0,0 +1,648 @@ +//! Import request collections from VSCode `.rest` files or Jetbrains `.http` +//! files. VSCode: https://github.com/Huachao/vscode-restclient +//! Jetbrains: https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html + +use anyhow::anyhow; +use indexmap::IndexMap; +use itertools::Itertools; +use serde::de::IgnoredAny; +use slumber_core::{ + collection::{ + Authentication, Chain, ChainId, ChainOutputTrim, ChainSource, + Collection, HasId, Method, Profile, ProfileId, Recipe, RecipeBody, + RecipeId, RecipeNode, RecipeTree, SelectorMode, + }, + http::content_type::ContentType, + template::{Identifier, Template}, + util::ResultTraced, +}; + +use reqwest::header; +use rest_parser::{ + headers::Authorization as RestAuthorization, + template::{Template as RestTemplate, TemplatePart as RestTemplatePart}, + Body as RestBody, RestFlavor, RestFormat, RestRequest, RestVariables, +}; +use std::path::Path; +use tracing::error; + +/// Convert a VSCode `.rest` file or a Jetbrains `.http` file into a slumber +/// collection +pub fn from_rest(rest_file: impl AsRef) -> anyhow::Result { + let rest_file = rest_file.as_ref(); + // Parse the file and determine the flavor using the extension + let rest_format = RestFormat::parse_file(rest_file)?; + let collection = try_build_collection(rest_format)?; + Ok(collection) +} + +/// In Rest "Chains" and "Requests" are connected +/// The only chain is loading from a file +#[derive(Debug)] +struct CompleteRecipe { + recipe: Recipe, + chain: Option, +} + +#[derive(Debug)] +struct CompleteBody { + recipe_body: RecipeBody, + chain: Option, +} + +/// Convert a REST Template into a Slumber Template +fn try_build_slumber_template( + template: RestTemplate, +) -> anyhow::Result