├── .husky ├── .gitignore ├── pre-commit └── commit-msg ├── src ├── runtime │ ├── .gitignore │ ├── visitors │ │ └── generics │ │ │ ├── MessageBodyVisitor.js │ │ │ ├── AsteriskFormVisitor.js │ │ │ ├── AbsolutePathVisitor.js │ │ │ ├── OriginFormVisitor.js │ │ │ ├── AbsoluteFormVisitor.js │ │ │ └── RequestTargetVisitor.js │ ├── index.js │ └── runners │ │ └── axios │ │ ├── index.js │ │ └── AxiosVisitor.js ├── parser │ ├── cst │ │ ├── host.js │ │ ├── headers.js │ │ ├── messages.js │ │ ├── port.js │ │ ├── authority.js │ │ ├── hier-part.js │ │ ├── query.js │ │ ├── method.js │ │ ├── origin-form.js │ │ ├── scheme.js │ │ ├── fragment.js │ │ ├── literal.js │ │ ├── message-body.js │ │ ├── response-ref.js │ │ ├── segment.js │ │ ├── absolute-form.js │ │ ├── absolute-path.js │ │ ├── file-path.js │ │ ├── input-file-ref.js │ │ ├── field-name.js │ │ ├── field-value.js │ │ ├── request-target.js │ │ ├── http-version.js │ │ ├── ipv6-address.js │ │ ├── message-line.js │ │ ├── response-handler.js │ │ ├── asterisk-form.js │ │ ├── handler-script.js │ │ ├── path-separator.js │ │ ├── ipv4-or-reg-name.js │ │ ├── requests-file.js │ │ ├── value-node.js │ │ ├── request.js │ │ ├── env-variable.js │ │ ├── node.js │ │ ├── header-field.js │ │ ├── request-line.js │ │ ├── predicates.js │ │ └── index.js │ ├── index.js │ ├── sexprs │ │ └── index.js │ ├── grammar.ne │ ├── postprocessors.js │ └── grammar.js ├── index.js └── visitor.js ├── .gitattributes ├── .npmrc ├── .eslintignore ├── .lintstagedrc ├── NOTICE ├── test ├── parser │ ├── fixtures │ │ └── http │ │ │ ├── evironment-variables │ │ │ ├── 00005.http │ │ │ ├── 00002.http │ │ │ ├── 00001.http │ │ │ ├── 00003.http │ │ │ └── 00004.http │ │ │ └── 00000.http │ ├── .mocharc.js │ └── index.js ├── runtime │ ├── .mocharc.js │ ├── runners │ │ └── axios │ │ │ └── AxiosVisitor │ │ │ ├── fixtures │ │ │ └── single-request.http │ │ │ └── index.js │ └── index.js ├── .eslintrc └── corpus │ └── corpus.js ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── FUNDING.yml ├── dependabot.yml ├── workflows │ ├── dependabot-merge.yml │ ├── nodejs.yml │ └── codeql.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .gitignore ├── .commitlintrc.json ├── .prettierrc ├── scripts ├── 00000-sexprs.js └── corpus-regenerate.js ├── .npmignore ├── .eslintrc ├── .editorconfig ├── SECURITY.md ├── package.json ├── CODE_OF_CONDUCT.md ├── README.md ├── CONTRIBUTING.md ├── GOVERNANCE.md └── LICENSE.txt /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ -------------------------------------------------------------------------------- /src/runtime/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | core.autocrlf=false 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-prefix="=" 2 | save=false 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /.tmp 2 | /src/parser/grammar.js -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.js": "eslint" 3 | } 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | lint-staged -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | commitlint -e 5 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | http-request-in-editor 2 | Copyright 2021 Vladimír Gorej 3 | -------------------------------------------------------------------------------- /test/parser/fixtures/http/evironment-variables/00005.http: -------------------------------------------------------------------------------- 1 | GET {{ schema }}://{{host}}/api/get?id={{ element-id }} 2 | 3 | ### 4 | -------------------------------------------------------------------------------- /test/runtime/.mocharc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | recursive: true, 5 | spec: 'test/runtime/', 6 | }; 7 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Pull request template 2 | 3 | Please follow instructions in [CONTRIBUTING.md](./CONTRIBUTING.md). 4 | -------------------------------------------------------------------------------- /test/parser/fixtures/http/evironment-variables/00002.http: -------------------------------------------------------------------------------- 1 | GET http://www.example.com 2 | 3 | body 4 | {{ var }} 5 | body 6 | 7 | ### 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.tmp 3 | /.nyc_output 4 | /node_modules 5 | /npm-debug.log 6 | 7 | /http-request-in-editor-*.*.*.tgz 8 | /railroad.html 9 | -------------------------------------------------------------------------------- /test/parser/.mocharc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | recursive: true, 5 | spec: ['test/parser/', 'test/corpus/'], 6 | }; 7 | -------------------------------------------------------------------------------- /test/parser/fixtures/http/evironment-variables/00001.http: -------------------------------------------------------------------------------- 1 | POST http://www.example.com/{{ $variable1Z-23_ }} HTTP/2.0 2 | Header: {{ value }} 3 | 4 | ### 5 | -------------------------------------------------------------------------------- /test/parser/fixtures/http/evironment-variables/00003.http: -------------------------------------------------------------------------------- 1 | GET http://www.example.com 2 | {{ name }}: http://example.com:8080 3 | Name: {{ value }} 4 | 5 | ### 6 | -------------------------------------------------------------------------------- /test/parser/fixtures/http/evironment-variables/00004.http: -------------------------------------------------------------------------------- 1 | GET {{ var }} 2 | Host:http://example.com:8080 3 | Host: http://example.com:8080 4 | Host: 5 | Host: value 6 | 7 | ### 8 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [char0n] 4 | patreon: char0n 5 | ko_fi: char0n 6 | liberapay: char0n 7 | issuehunt: char0n 8 | # otechie: # Replace with a single Otechie username 9 | -------------------------------------------------------------------------------- /test/parser/fixtures/http/00000.http: -------------------------------------------------------------------------------- 1 | ### 2 | POST https://httpbin.org 3 | /post 4 | ?search1=value1 5 | &search2=value2 6 | #fragment1 7 | fragment2 8 | Content-type: application/json 9 | 10 | {} 11 | 12 | ### 13 | -------------------------------------------------------------------------------- /src/parser/cst/host.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const Node = require('./node'); 6 | 7 | const Host = stampit(Node, { 8 | statics: { 9 | type: 'host', 10 | }, 11 | }); 12 | 13 | module.exports = Host; 14 | -------------------------------------------------------------------------------- /test/runtime/runners/axios/AxiosVisitor/fixtures/single-request.http: -------------------------------------------------------------------------------- 1 | ### 2 | POST https://httpbin.org 3 | /post 4 | ?search1=value1 5 | &search2=value2 6 | #fragment1 7 | fragment2 8 | Content-Type: application/json 9 | 10 | {} 11 | 12 | ### 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | commit-message: 8 | prefix: "chore" 9 | include: "scope" 10 | open-pull-requests-limit: 10 11 | -------------------------------------------------------------------------------- /src/parser/cst/headers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const Node = require('./node'); 6 | 7 | const Headers = stampit(Node, { 8 | statics: { 9 | type: 'headers', 10 | }, 11 | }); 12 | 13 | module.exports = Headers; 14 | -------------------------------------------------------------------------------- /src/parser/cst/messages.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const Node = require('./node'); 6 | 7 | const Messages = stampit(Node, { 8 | statics: { 9 | type: 'messages', 10 | }, 11 | }); 12 | 13 | module.exports = Messages; 14 | -------------------------------------------------------------------------------- /src/parser/cst/port.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const Port = stampit(ValueNode, { 8 | statics: { 9 | type: 'port', 10 | }, 11 | }); 12 | 13 | module.exports = Port; 14 | -------------------------------------------------------------------------------- /src/parser/cst/authority.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const Node = require('./node'); 6 | 7 | const Authority = stampit(Node, { 8 | statics: { 9 | type: 'authority', 10 | }, 11 | }); 12 | 13 | module.exports = Authority; 14 | -------------------------------------------------------------------------------- /src/parser/cst/hier-part.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const Node = require('./node'); 6 | 7 | const HierPart = stampit(Node, { 8 | statics: { 9 | type: 'hier-part', 10 | }, 11 | }); 12 | 13 | module.exports = HierPart; 14 | -------------------------------------------------------------------------------- /src/parser/cst/query.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const Query = stampit(ValueNode, { 8 | statics: { 9 | type: 'query', 10 | }, 11 | }); 12 | 13 | module.exports = Query; 14 | -------------------------------------------------------------------------------- /src/parser/cst/method.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const Method = stampit(ValueNode, { 8 | statics: { 9 | type: 'method', 10 | }, 11 | }); 12 | 13 | module.exports = Method; 14 | -------------------------------------------------------------------------------- /src/parser/cst/origin-form.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const Node = require('./node'); 6 | 7 | const OriginForm = stampit(Node, { 8 | statics: { 9 | type: 'origin-form', 10 | }, 11 | }); 12 | 13 | module.exports = OriginForm; 14 | -------------------------------------------------------------------------------- /src/parser/cst/scheme.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const Scheme = stampit(ValueNode, { 8 | statics: { 9 | type: 'scheme', 10 | }, 11 | }); 12 | 13 | module.exports = Scheme; 14 | -------------------------------------------------------------------------------- /src/parser/cst/fragment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const Fragment = stampit(ValueNode, { 8 | statics: { 9 | type: 'fragment', 10 | }, 11 | }); 12 | 13 | module.exports = Fragment; 14 | -------------------------------------------------------------------------------- /src/parser/cst/literal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const Literal = stampit(ValueNode, { 8 | statics: { 9 | type: 'literal', 10 | }, 11 | }); 12 | 13 | module.exports = Literal; 14 | -------------------------------------------------------------------------------- /src/parser/cst/message-body.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const Node = require('./node'); 6 | 7 | const MessageBody = stampit(Node, { 8 | statics: { 9 | type: 'message-body', 10 | }, 11 | }); 12 | 13 | module.exports = MessageBody; 14 | -------------------------------------------------------------------------------- /src/parser/cst/response-ref.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const Node = require('./node'); 6 | 7 | const ResponseRef = stampit(Node, { 8 | statics: { 9 | type: 'response-ref', 10 | }, 11 | }); 12 | 13 | module.exports = ResponseRef; 14 | -------------------------------------------------------------------------------- /src/parser/cst/segment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const Segment = stampit(ValueNode, { 8 | statics: { 9 | type: 'segment', 10 | }, 11 | }); 12 | 13 | module.exports = Segment; 14 | -------------------------------------------------------------------------------- /src/parser/cst/absolute-form.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const Node = require('./node'); 6 | 7 | const AbsoluteForm = stampit(Node, { 8 | statics: { 9 | type: 'absolute-form', 10 | }, 11 | }); 12 | 13 | module.exports = AbsoluteForm; 14 | -------------------------------------------------------------------------------- /src/parser/cst/absolute-path.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const Node = require('./node'); 6 | 7 | const AbsolutePath = stampit(Node, { 8 | statics: { 9 | type: 'absolute-path', 10 | }, 11 | }); 12 | 13 | module.exports = AbsolutePath; 14 | -------------------------------------------------------------------------------- /src/parser/cst/file-path.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const FilePath = stampit(ValueNode, { 8 | statics: { 9 | type: 'file-path', 10 | }, 11 | }); 12 | 13 | module.exports = FilePath; 14 | -------------------------------------------------------------------------------- /src/parser/cst/input-file-ref.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const Node = require('./node'); 6 | 7 | const InputFileRef = stampit(Node, { 8 | statics: { 9 | type: 'input-file-ref', 10 | }, 11 | }); 12 | 13 | module.exports = InputFileRef; 14 | -------------------------------------------------------------------------------- /src/parser/cst/field-name.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const FieldName = stampit(ValueNode, { 8 | statics: { 9 | type: 'field-name', 10 | }, 11 | }); 12 | 13 | module.exports = FieldName; 14 | -------------------------------------------------------------------------------- /src/parser/cst/field-value.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const FieldValue = stampit(ValueNode, { 8 | statics: { 9 | type: 'field-value', 10 | }, 11 | }); 12 | 13 | module.exports = FieldValue; 14 | -------------------------------------------------------------------------------- /src/parser/cst/request-target.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const Node = require('./node'); 6 | 7 | const RequestTarget = stampit(Node, { 8 | statics: { 9 | type: 'request-target', 10 | }, 11 | }); 12 | 13 | module.exports = RequestTarget; 14 | -------------------------------------------------------------------------------- /src/parser/cst/http-version.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const HttpVersion = stampit(ValueNode, { 8 | statics: { 9 | type: 'http-version', 10 | }, 11 | }); 12 | 13 | module.exports = HttpVersion; 14 | -------------------------------------------------------------------------------- /src/parser/cst/ipv6-address.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const Ipv6Address = stampit(ValueNode, { 8 | statics: { 9 | type: 'ipv6-address', 10 | }, 11 | }); 12 | 13 | module.exports = Ipv6Address; 14 | -------------------------------------------------------------------------------- /src/parser/cst/message-line.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const MessageLine = stampit(ValueNode, { 8 | statics: { 9 | type: 'message-line', 10 | }, 11 | }); 12 | 13 | module.exports = MessageLine; 14 | -------------------------------------------------------------------------------- /src/parser/cst/response-handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const Node = require('./node'); 6 | 7 | const ResponseHandler = stampit(Node, { 8 | statics: { 9 | type: 'response-handler', 10 | }, 11 | }); 12 | 13 | module.exports = ResponseHandler; 14 | -------------------------------------------------------------------------------- /src/parser/cst/asterisk-form.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const AsteriskForm = stampit(ValueNode, { 8 | statics: { 9 | type: 'asterisk-form', 10 | }, 11 | }); 12 | 13 | module.exports = AsteriskForm; 14 | -------------------------------------------------------------------------------- /src/parser/cst/handler-script.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const HandlerScript = stampit(ValueNode, { 8 | statics: { 9 | type: 'handler-script', 10 | }, 11 | }); 12 | 13 | module.exports = HandlerScript; 14 | -------------------------------------------------------------------------------- /src/parser/cst/path-separator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const PathSeparator = stampit(ValueNode, { 8 | statics: { 9 | type: 'path-separator', 10 | }, 11 | }); 12 | 13 | module.exports = PathSeparator; 14 | -------------------------------------------------------------------------------- /src/parser/cst/ipv4-or-reg-name.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const Ipv4OrRegName = stampit(ValueNode, { 8 | statics: { 9 | type: 'ipv4-or-reg-name', 10 | }, 11 | }); 12 | 13 | module.exports = Ipv4OrRegName; 14 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@commitlint/config-conventional" 4 | ], 5 | "rules": { 6 | "scope-case": [ 7 | 2, 8 | "always", 9 | [ 10 | "camel-case", 11 | "kebab-case", 12 | "upper-case" 13 | ] 14 | ], 15 | "subject-case": [ 16 | 0, 17 | "always" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true, 6 | "trailingComma": "es5", 7 | "overrides": [ 8 | { 9 | "files": ".prettierrc", 10 | "options": { "parser": "json" } 11 | }, 12 | { 13 | "files": ".eslintrc", 14 | "options": { "parser": "json" } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/parser/cst/requests-file.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const Node = require('./node'); 6 | 7 | const RequestsFile = stampit(Node, { 8 | statics: { 9 | type: 'requests-file', 10 | }, 11 | methods: { 12 | get requests() { 13 | return this.children; 14 | }, 15 | }, 16 | }); 17 | 18 | module.exports = RequestsFile; 19 | -------------------------------------------------------------------------------- /src/runtime/visitors/generics/MessageBodyVisitor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const MessageBodyVisitor = stampit({ 6 | props: { 7 | messageBody: '', 8 | }, 9 | methods: { 10 | 'message-line': function messageLine(node) { 11 | this.messageBody += node.value; 12 | }, 13 | }, 14 | }); 15 | 16 | module.exports = MessageBodyVisitor; 17 | -------------------------------------------------------------------------------- /src/parser/cst/value-node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const Node = require('./node'); 6 | 7 | const ValueNode = stampit(Node, { 8 | statics: { 9 | type: 'value-node', 10 | }, 11 | props: { 12 | value: null, 13 | }, 14 | init({ value = this.value } = {}) { 15 | this.value = value; 16 | }, 17 | }); 18 | 19 | module.exports = ValueNode; 20 | -------------------------------------------------------------------------------- /src/runtime/visitors/generics/AsteriskFormVisitor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const AsteriskFormVisitor = stampit({ 6 | props: { 7 | asteriskForm: '', 8 | }, 9 | methods: { 10 | 'asterisk-form': function asteriskForm(node) { 11 | this.asteriskForm = node.value; 12 | }, 13 | }, 14 | }); 15 | 16 | module.exports = AsteriskFormVisitor; 17 | -------------------------------------------------------------------------------- /src/parser/cst/request.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | const { defaultTo } = require('ramda'); 5 | 6 | const Node = require('./node'); 7 | 8 | const Request = stampit(Node, { 9 | statics: { 10 | type: 'request', 11 | }, 12 | methods: { 13 | get requestLine() { 14 | return defaultTo(null, this.children[0]); 15 | }, 16 | }, 17 | }); 18 | 19 | module.exports = Request; 20 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-merge.yml: -------------------------------------------------------------------------------- 1 | name: Merge me! 2 | 3 | on: 4 | pull_request_target: 5 | 6 | jobs: 7 | merge-me: 8 | name: Merge me! 9 | if: github.actor == 'dependabot[bot]' 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Merge me! 13 | uses: ahmadnassri/action-dependabot-auto-merge@v2 14 | with: 15 | target: minor 16 | github-token: ${{ secrets.MERGE_ME_GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /src/runtime/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { pathOr } = require('ramda'); 4 | 5 | const { parse } = require('../parser'); 6 | const AxiosRunner = require('./runners/axios'); 7 | 8 | const runSpec = async (httpSpec, options = {}) => { 9 | const cstTree = parse(httpSpec); 10 | const runner = pathOr(AxiosRunner, ['runner'], options)({ cstTree }); 11 | return runner.run(); 12 | }; 13 | 14 | module.exports = { 15 | runSpec, 16 | }; 17 | -------------------------------------------------------------------------------- /scripts/00000-sexprs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | const { parse, sexprs } = require('../src'); 7 | 8 | const httpPath = path.join( 9 | __dirname, 10 | '..', 11 | 'test/parser/fixtures/http/00000.http' 12 | ); 13 | const http = fs.readFileSync(httpPath).toString(); 14 | const cstTree = parse(http); 15 | 16 | // eslint-disable-next-line no-console 17 | console.log(sexprs(cstTree)); 18 | -------------------------------------------------------------------------------- /src/parser/cst/env-variable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const ValueNode = require('./value-node'); 6 | 7 | const EnvVariable = stampit(ValueNode, { 8 | statics: { 9 | type: 'env-variable', 10 | }, 11 | methods: { 12 | get name() { 13 | return this.value.match(/^{{\s*(?[a-zA-Z0-9_-]+)\s*}}$/) 14 | .groups.variable_name; 15 | }, 16 | }, 17 | }); 18 | 19 | module.exports = EnvVariable; 20 | -------------------------------------------------------------------------------- /src/parser/cst/node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const Node = stampit({ 6 | statics: { 7 | type: 'node', 8 | }, 9 | props: { 10 | type: null, 11 | location: null, 12 | children: [], 13 | }, 14 | init({ location = this.location, children = this.children }, { stamp }) { 15 | this.type = stamp.type; 16 | this.location = location; 17 | this.children = children; 18 | }, 19 | }); 20 | 21 | module.exports = Node; 22 | -------------------------------------------------------------------------------- /src/parser/cst/header-field.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | const { head, last } = require('ramda'); 5 | 6 | const Node = require('./node'); 7 | 8 | const HeaderField = stampit(Node, { 9 | statics: { 10 | type: 'header-field', 11 | }, 12 | methods: { 13 | get fieldName() { 14 | return head(this.children); 15 | }, 16 | get fieldValue() { 17 | return last(this.children); 18 | }, 19 | }, 20 | }); 21 | 22 | module.exports = HeaderField; 23 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.tmp 3 | /.nyc_output 4 | /.github 5 | /.husky 6 | /node_modules 7 | /npm-debug.log 8 | /scripts/**/* 9 | /test 10 | /.dependabot 11 | /.huskyrc 12 | /.lintstagedrc 13 | /.editorconfig 14 | /.eslintrc 15 | /.eslintignore 16 | /.gitattributes 17 | /.gitignore 18 | /.mocharc.js 19 | /.npmignore 20 | /.npmrc 21 | /.commitlintrc.json 22 | /.prettierignore 23 | /.prettierrc 24 | /CODE_OF_CONDUCT.md 25 | /CONTRIBUTING.md 26 | /GOVERNANCE.md 27 | /http-request-in-editor-*.*.*.tgz 28 | /railroad.html 29 | -------------------------------------------------------------------------------- /src/runtime/visitors/generics/AbsolutePathVisitor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { trim } = require('ramda'); 4 | const stampit = require('stampit'); 5 | 6 | const AbsolutePathVisitor = stampit({ 7 | props: { 8 | absolutePath: '', 9 | }, 10 | methods: { 11 | 'path-separator': function pathSeparator(node) { 12 | this.absolutePath += trim(node.value); 13 | }, 14 | segment(node) { 15 | this.absolutePath += node.value; 16 | }, 17 | }, 18 | }); 19 | 20 | module.exports = AbsolutePathVisitor; 21 | -------------------------------------------------------------------------------- /src/parser/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nearley = require('nearley'); 4 | 5 | const grammar = require('./grammar'); 6 | 7 | const compiledGrammar = nearley.Grammar.fromCompiled(grammar); 8 | 9 | // createParser :: () -> Parser 10 | const createParser = () => new nearley.Parser(compiledGrammar); 11 | 12 | // parse :: String -> Array 13 | const parse = (http) => { 14 | const parser = createParser(); 15 | parser.feed(http); 16 | return parser.results; 17 | }; 18 | 19 | module.exports = { 20 | createParser, 21 | parse, 22 | }; 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/runtime/runners/axios/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | const axios = require('axios'); 5 | const { allSettledP } = require('ramda-adjunct'); 6 | 7 | const AxiosVisitor = require('./AxiosVisitor'); 8 | const { visit } = require('../../../visitor'); 9 | 10 | const AxiosRunner = stampit({ 11 | props: { 12 | cstTree: null, 13 | visitor: null, 14 | }, 15 | init({ cstTree }) { 16 | this.cstTree = cstTree; 17 | this.visitor = AxiosVisitor(); 18 | }, 19 | methods: { 20 | async run() { 21 | visit(this.cstTree, this.visitor); 22 | const { configs } = this.visitor; 23 | 24 | return allSettledP(configs.map(axios)); 25 | }, 26 | }, 27 | }); 28 | 29 | module.exports = AxiosRunner; 30 | -------------------------------------------------------------------------------- /src/parser/cst/request-line.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | const { defaultTo, pipe, find } = require('ramda'); 5 | 6 | const Node = require('./node'); 7 | const { isMethod, isRequestTarget, isHttpVersion } = require('./predicates'); 8 | 9 | const RequestLine = stampit(Node, { 10 | statics: { 11 | type: 'requests-line', 12 | }, 13 | methods: { 14 | get method() { 15 | return pipe(defaultTo(null), find(isMethod))(this.children); 16 | }, 17 | get requestTarget() { 18 | return pipe(defaultTo(null), find(isRequestTarget))(this.children); 19 | }, 20 | get httpVersion() { 21 | return pipe(defaultTo(null), find(isHttpVersion))(this.children); 22 | }, 23 | }, 24 | }); 25 | 26 | module.exports = RequestLine; 27 | -------------------------------------------------------------------------------- /src/runtime/visitors/generics/OriginFormVisitor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const { visit } = require('../../../visitor'); 6 | const AbsolutePathVisitor = require('./AbsolutePathVisitor'); 7 | 8 | const OriginFormVisitor = stampit({ 9 | props: { 10 | originForm: '', 11 | }, 12 | methods: { 13 | 'absolute-path': function absolutePath(node) { 14 | const visitor = AbsolutePathVisitor(); 15 | 16 | visit(node, visitor); 17 | this.originForm += visitor.absolutePath; 18 | 19 | return false; 20 | }, 21 | query(node) { 22 | this.originForm += `?${node.value}`; 23 | }, 24 | fragment(node) { 25 | this.originForm += `#${node.value}`; 26 | }, 27 | }, 28 | }); 29 | 30 | module.exports = OriginFormVisitor; 31 | -------------------------------------------------------------------------------- /src/parser/sexprs/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const { visit } = require('../../visitor'); 6 | 7 | const SexpressionVisitor = stampit({ 8 | props: { 9 | nestingLevel: 0, 10 | result: '', 11 | }, 12 | methods: { 13 | enter(node) { 14 | const indent = ' '.repeat(this.nestingLevel); 15 | this.result += this.nestingLevel > 0 ? '\n' : ''; 16 | this.result += `${indent}(${node.type}`; 17 | this.nestingLevel += 1; 18 | }, 19 | leave() { 20 | this.nestingLevel -= 1; 21 | this.result += ')'; 22 | }, 23 | }, 24 | }); 25 | 26 | const sexprs = (cst) => { 27 | const visitor = SexpressionVisitor(); 28 | visit(cst, visitor); 29 | return visitor.result; 30 | }; 31 | 32 | module.exports = { 33 | sexprs, 34 | }; 35 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "node": true 5 | }, 6 | "parserOptions": { 7 | "sourceType": "script", 8 | "ecmaFeatures": { 9 | "impliedStrict": false 10 | } 11 | }, 12 | "extends": [ 13 | "eslint-config-airbnb-base", 14 | "prettier" 15 | ], 16 | "plugins": ["eslint-plugin-prettier", "prettier"], 17 | "rules": { 18 | "strict": [2, "global"], 19 | "space-before-function-paren": "off", 20 | "object-curly-newline": "off", 21 | "import/order": ["error", { 22 | "groups": [ 23 | ["builtin", "external", "internal"], 24 | ["parent", "sibling", "index"] 25 | ], 26 | "newlines-between": "always" 27 | }], 28 | "quotes": ["error", "single", { "avoidEscape": true }], 29 | "prettier/prettier": "error" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.x, 14.x, 16.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm ci 28 | - run: npm run build --if-present 29 | - run: npm run lint 30 | - run: npm test 31 | env: 32 | CI: true 33 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | # A special property that should be specified at the top of the file outside of 4 | # any sections. Set to true to stop .editor config file search on current file 5 | root = true 6 | 7 | [*] 8 | # Indentation style 9 | # Possible values - tab, space 10 | indent_style = space 11 | 12 | # Indentation size in single-spaced characters 13 | # Possible values - an integer, tab 14 | indent_size = 2 15 | 16 | # Line ending file format 17 | # Possible values - lf, crlf, cr 18 | end_of_line = lf 19 | 20 | # File character encoding 21 | # Possible values - latin1, utf-8, utf-16be, utf-16le 22 | charset = utf-8 23 | 24 | # Denotes whether to trim whitespace at the end of lines 25 | # Possible values - true, false 26 | trim_trailing_whitespace = true 27 | 28 | # Denotes whether file should end with a newline 29 | # Possible values - true, false 30 | insert_final_newline = true 31 | 32 | [*.ts] 33 | indent_size = 4 34 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "document": true 7 | }, 8 | "plugins": [ 9 | "mocha" 10 | ], 11 | "rules": { 12 | "no-void": 0, 13 | "func-names": 0, 14 | "prefer-arrow-callback": 0, 15 | "no-array-constructor": 0, 16 | "prefer-rest-params": 0, 17 | "no-new-wrappers": 0, 18 | "import/no-extraneous-dependencies": ["error", {"devDependencies": true}], 19 | "mocha/no-skipped-tests": 2, 20 | "mocha/handle-done-callback": 2, 21 | "mocha/valid-suite-description": 2, 22 | "mocha/no-mocha-arrows": 2, 23 | "mocha/no-hooks-for-single-case": 2, 24 | "mocha/no-sibling-hooks": 2, 25 | "mocha/no-top-level-hooks": 2, 26 | "mocha/no-identical-title": 2, 27 | "mocha/no-nested-tests": 2, 28 | "mocha/no-exclusive-tests": 2 29 | }, 30 | "overrides": [{ 31 | "files": ["mocha-bootstrap.js"], 32 | "parserOptions": { 33 | "sourceType": "script" 34 | } 35 | }] 36 | } 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /test/runtime/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { assert } = require('chai'); 4 | 5 | const { runSpec } = require('../../src'); 6 | const AxiosRunner = require('../../src/runtime/runners/axios'); 7 | 8 | /** 9 | * These are e2e tests doing actual requests to `request-target`. 10 | */ 11 | 12 | describe('runtime', function () { 13 | context('runSpec', function () { 14 | context('given no options provided', function () { 15 | specify('should run the spec using AxiosRunner', async function () { 16 | const httpSpec = 'GET https://vladimirgorej.com/\n\n'; 17 | const actual = await runSpec(httpSpec); 18 | 19 | assert.lengthOf(actual, 1); 20 | }); 21 | }); 22 | 23 | context('given AxiosRunner provided as option', function () { 24 | specify('should run the spec using AxiosRunner', async function () { 25 | const httpSpec = 'GET https://vladimirgorej.com/\n\n'; 26 | const actual = await runSpec(httpSpec, { runner: AxiosRunner }); 27 | 28 | assert.lengthOf(actual, 1); 29 | }); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { parse } = require('./parser'); 4 | const { sexprs } = require('./parser/sexprs'); 5 | const { runSpec } = require('./runtime'); 6 | const AxiosVisitor = require('./runtime/runners/axios/AxiosVisitor'); 7 | const AxiosRunner = require('./runtime/runners/axios'); 8 | const AbsoluteFormVisitor = require('./runtime/visitors/generics/AbsoluteFormVisitor'); 9 | const AbsolutePathVisitor = require('./runtime/visitors/generics/AbsolutePathVisitor'); 10 | const AsteriskFormVisitor = require('./runtime/visitors/generics/AsteriskFormVisitor'); 11 | const MessageBodyVisitor = require('./runtime/visitors/generics/MessageBodyVisitor'); 12 | const OriginFormVisitor = require('./runtime/visitors/generics/OriginFormVisitor'); 13 | const RequestTargetVisitor = require('./runtime/visitors/generics/RequestTargetVisitor'); 14 | 15 | module.exports = { 16 | parse, 17 | sexprs, 18 | runSpec, 19 | AxiosVisitor, 20 | AxiosRunner, 21 | AbsoluteFormVisitor, 22 | AbsolutePathVisitor, 23 | AsteriskFormVisitor, 24 | MessageBodyVisitor, 25 | OriginFormVisitor, 26 | RequestTargetVisitor, 27 | }; 28 | -------------------------------------------------------------------------------- /src/runtime/visitors/generics/AbsoluteFormVisitor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const { visit } = require('../../../visitor'); 6 | const AbsolutePathVisitor = require('./AbsolutePathVisitor'); 7 | 8 | const AbsoluteFormVisitor = stampit({ 9 | props: { 10 | absoluteForm: '', 11 | }, 12 | methods: { 13 | scheme(node) { 14 | this.absoluteForm = `${node.value}://`; 15 | }, 16 | 'ipv4-or-reg-name': function ipv4OrRegName(node) { 17 | this.absoluteForm += node.value; 18 | }, 19 | 'ipv6-address': function ipv6Address(node) { 20 | this.absoluteForm += `[${node.value}]`; 21 | }, 22 | 'absolute-path': function absolutePath(node) { 23 | const visitor = AbsolutePathVisitor(); 24 | 25 | visit(node, visitor); 26 | this.absoluteForm += visitor.absolutePath; 27 | 28 | return false; 29 | }, 30 | query(node) { 31 | this.absoluteForm += `?${node.value}`; 32 | }, 33 | fragment(node) { 34 | this.absoluteForm += `#${node.value}`; 35 | }, 36 | }, 37 | }); 38 | 39 | module.exports = AbsoluteFormVisitor; 40 | -------------------------------------------------------------------------------- /test/corpus/corpus.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const { assert } = require('chai'); 6 | const { trim, split, map, tail, splitEvery, pipe } = require('ramda'); 7 | 8 | const { parse } = require('../../src/parser'); 9 | const { sexprs } = require('../../src'); 10 | 11 | const documentSeparator = '='.repeat(80); 12 | const httpCSTSeparator = '-'.repeat(80); 13 | const transformer = pipe( 14 | split(documentSeparator), 15 | tail, 16 | splitEvery(2), 17 | map(([header, httpCSTPair]) => { 18 | const [http, cstRep] = split(httpCSTSeparator, httpCSTPair); 19 | return [trim(header), http, trim(cstRep)]; 20 | }) 21 | ); 22 | const corpus = transformer( 23 | fs.readFileSync(path.join(__dirname, 'corpus.txt')).toString() 24 | ); 25 | 26 | describe('corpus', function () { 27 | corpus.forEach(([header, http, cstRep]) => { 28 | context(header, function () { 29 | specify('should verify corpus record', function () { 30 | const cstTree = parse(http); 31 | 32 | assert.lengthOf(cstTree, 1); 33 | assert.strictEqual(sexprs(cstTree[0]), cstRep); 34 | }); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /scripts/corpus-regenerate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const { trim, split, map, tail, splitEvery, pipe } = require('ramda'); 6 | 7 | const { parse, sexprs } = require('../src'); 8 | 9 | const documentSeparator = '='.repeat(80); 10 | const httpCSTSeparator = '-'.repeat(80); 11 | const transformer = pipe( 12 | split(documentSeparator), 13 | tail, 14 | splitEvery(2), 15 | map(([header, httpAstPair]) => { 16 | const [http, sExpression] = split(httpCSTSeparator, httpAstPair); 17 | return [trim(header), http, trim(sExpression)]; 18 | }) 19 | ); 20 | const corpusPath = path.join(__dirname, '..', 'test', 'corpus', 'corpus.txt'); 21 | const corpus = transformer(fs.readFileSync(corpusPath).toString()); 22 | 23 | const regeneratedCorpus = corpus 24 | .map(([header, http]) => { 25 | const cstTree = parse(http); 26 | 27 | return `${documentSeparator}\n${header}\n${documentSeparator}${http}${httpCSTSeparator}\n\n${sexprs( 28 | cstTree 29 | )}\n\n`; 30 | }) 31 | .join(''); 32 | 33 | fs.writeFileSync(corpusPath, regeneratedCorpus); 34 | 35 | // eslint-disable-next-line no-console 36 | console.info('Corpus successfully regenerated.'); 37 | -------------------------------------------------------------------------------- /src/parser/cst/predicates.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { pathEq } = require('ramda'); 4 | 5 | const isNodeType = pathEq(['type']); 6 | 7 | const isMethod = isNodeType('method'); 8 | 9 | const isRequestTarget = isNodeType('request-target'); 10 | 11 | const isHttpVersion = isNodeType('http-version'); 12 | 13 | const isOriginForm = isNodeType('origin-form'); 14 | 15 | const isAbsolutePath = isNodeType('absolute-path'); 16 | 17 | const isLiteral = isNodeType('literal'); 18 | 19 | const isFilePath = isNodeType('file-path'); 20 | 21 | const isHeaders = isNodeType('headers'); 22 | 23 | const isMessageBody = isNodeType('message-body'); 24 | 25 | const isResponseHandler = isNodeType('response-handler'); 26 | 27 | const isResponseRef = isNodeType('response-ref'); 28 | 29 | const isQuery = isNodeType('query'); 30 | 31 | const isFragment = isNodeType('fragment'); 32 | 33 | const isEnvVariable = isNodeType('env-variable'); 34 | 35 | module.exports = { 36 | isMethod, 37 | isRequestTarget, 38 | isHttpVersion, 39 | isOriginForm, 40 | isAbsolutePath, 41 | isLiteral, 42 | isFilePath, 43 | isHeaders, 44 | isMessageBody, 45 | isResponseHandler, 46 | isResponseRef, 47 | isQuery, 48 | isFragment, 49 | isEnvVariable, 50 | }; 51 | -------------------------------------------------------------------------------- /src/runtime/visitors/generics/RequestTargetVisitor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const { BREAK, visit } = require('../../../visitor'); 6 | const OriginFormVisitor = require('./OriginFormVisitor'); 7 | const AbsoluteFormVisitor = require('./AbsoluteFormVisitor'); 8 | const AsteriskFormVisitor = require('./AsteriskFormVisitor'); 9 | 10 | const RequestTargetVisitor = stampit({ 11 | prop: { 12 | requestTarget: '', 13 | }, 14 | methods: { 15 | 'origin-form': function originForm(node) { 16 | const visitor = OriginFormVisitor(); 17 | 18 | visit(node, visitor); 19 | this.requestTarget = visitor.originForm; 20 | 21 | return BREAK; 22 | }, 23 | 'absolute-form': function absoluteForm(node) { 24 | const visitor = AbsoluteFormVisitor(); 25 | 26 | visit(node, visitor); 27 | this.requestTarget = visitor.absoluteForm; 28 | 29 | return BREAK; 30 | }, 31 | 'asterisk-form': function asteriskForm(node) { 32 | const visitor = AsteriskFormVisitor(); 33 | 34 | visit(node, visitor); 35 | this.requestTarget = visitor.asteriskForm; 36 | 37 | return BREAK; 38 | }, 39 | }, 40 | }); 41 | 42 | module.exports = RequestTargetVisitor; 43 | -------------------------------------------------------------------------------- /test/parser/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { assert } = require('chai'); 4 | const dedent = require('dedent'); 5 | const nearley = require('nearley'); 6 | 7 | const { parse, createParser } = require('../../src/parser'); 8 | 9 | describe('parser', function () { 10 | it('should expose proper public API', function () { 11 | assert.isFunction(createParser); 12 | assert.isFunction(parse); 13 | }); 14 | 15 | context('createParser', function () { 16 | specify('should return parser instance', function () { 17 | const parser = createParser(); 18 | 19 | assert.instanceOf(parser, nearley.Parser); 20 | }); 21 | }); 22 | 23 | context('parse', function () { 24 | context('given http fragment', function () { 25 | const http = dedent` 26 | GET http://www.example.com 27 | 28 | ### 29 | `; 30 | 31 | specify('should parse and return CST', function () { 32 | const actualCST = parse(http); 33 | 34 | assert.isArray(actualCST); 35 | }); 36 | }); 37 | 38 | context('given invalid http fragment', function () { 39 | const http = 'invalid http content'; 40 | 41 | specify('should throw Error', function () { 42 | const errorThunk = () => parse(http); 43 | 44 | assert.throws(errorThunk, Error); 45 | }); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /src/runtime/runners/axios/AxiosVisitor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stampit = require('stampit'); 4 | 5 | const { visit } = require('../../../visitor'); 6 | const RequestTargetVisitor = require('../../visitors/generics/RequestTargetVisitor'); 7 | const MessageBodyVisitor = require('../../visitors/generics/MessageBodyVisitor'); 8 | 9 | const AxiosVisitor = stampit({ 10 | props: { 11 | configs: [], 12 | config: {}, 13 | }, 14 | init() { 15 | this.configs = []; 16 | }, 17 | methods: { 18 | request() { 19 | this.config = {}; 20 | this.configs.push(this.config); 21 | }, 22 | method(node) { 23 | this.config.method = node.value.toLowerCase(); 24 | }, 25 | 'http-version': function httpVersion(node) { 26 | if (node.value === '2.0') { 27 | throw new Error("AxiosVisitor doesn't support HTTP/2.0"); 28 | } 29 | }, 30 | 'request-target': function requestTarget(node) { 31 | const visitor = RequestTargetVisitor(); 32 | 33 | visit(node, visitor); 34 | 35 | const url = new URL(visitor.requestTarget); 36 | 37 | this.config.url = url.pathname; 38 | this.config.baseURL = `${url.protocol}//${url.hostname}/`; 39 | this.config.params = url.searchParams; 40 | 41 | return false; 42 | }, 43 | headers() { 44 | this.config.headers = {}; 45 | }, 46 | 'header-field': function headerField(node) { 47 | this.config.headers[node.fieldName.value] = node.fieldValue.value; 48 | 49 | return false; 50 | }, 51 | 'message-body': function messageBody(node) { 52 | const visitor = MessageBodyVisitor(); 53 | 54 | visit(node, visitor); 55 | 56 | this.config.data = visitor.messageBody; 57 | 58 | return false; 59 | }, 60 | }, 61 | }); 62 | 63 | module.exports = AxiosVisitor; 64 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policies and Procedures 2 | 3 | This document outlines security procedures and general policies for the `http-request-in-editor` 4 | project. 5 | 6 | * [Reporting a Bug](#reporting-a-bug) 7 | * [Disclosure Policy](#disclosure-policy) 8 | * [Comments on this Policy](#comments-on-this-policy) 9 | 10 | ## Reporting a Bug 11 | 12 | The `http-request-in-editor` team and community take all security bugs in `http-request-in-editor` seriously. 13 | Thank you for improving the security of `http-request-in-editor`. We appreciate your efforts and 14 | responsible disclosure and will make every effort to acknowledge your 15 | contributions. 16 | 17 | Report security bugs by emailing the lead maintainer at **vladimir.gorej@gmail.com**. 18 | 19 | The lead maintainer will acknowledge your email within 48 hours, and will send a 20 | more detailed response within 48 hours indicating the next steps in handling 21 | your report. After the initial reply to your report, one of the team members will 22 | endeavor to keep you informed of the progress towards a fix and full 23 | announcement, and may ask for additional information or guidance. 24 | 25 | Report security bugs in third-party modules to the person or team maintaining 26 | the module, but still let us know that we have such a third-party module in our 27 | dependencies. 28 | 29 | ## Disclosure Policy 30 | 31 | When the team receives a security bug report, they will assign it to a 32 | primary handler. This person will coordinate the fix and release process, 33 | involving the following steps: 34 | 35 | * Confirm the problem and determine the affected versions. 36 | * Audit code to find any potential similar problems. 37 | * Prepare fixes for all releases still under maintenance. These fixes will be 38 | released as fast as possible to npm. 39 | 40 | ## Comments on this Policy 41 | 42 | If you have suggestions on how this process could be improved please submit a 43 | pull request. 44 | -------------------------------------------------------------------------------- /src/parser/cst/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Literal = require('./literal'); 4 | const RequestsFile = require('./requests-file'); 5 | const Request = require('./request'); 6 | const RequestLine = require('./request-line'); 7 | const RequestTarget = require('./request-target'); 8 | const OriginForm = require('./origin-form'); 9 | const AbsolutePath = require('./absolute-path'); 10 | const Query = require('./query'); 11 | const Fragment = require('./fragment'); 12 | const PathSeparator = require('./path-separator'); 13 | const Segment = require('./segment'); 14 | const AbsoluteForm = require('./absolute-form'); 15 | const HierPart = require('./hier-part'); 16 | const Authority = require('./authority'); 17 | const Host = require('./host'); 18 | const Port = require('./port'); 19 | const Ipv6Address = require('./ipv6-address'); 20 | const Ipv4OrRegName = require('./ipv4-or-reg-name'); 21 | const AsteriskForm = require('./asterisk-form'); 22 | const Scheme = require('./scheme'); 23 | const Method = require('./method'); 24 | const HttpVersion = require('./http-version'); 25 | const Headers = require('./headers'); 26 | const HeaderField = require('./header-field'); 27 | const FieldName = require('./field-name'); 28 | const FieldValue = require('./field-value'); 29 | const MessageBody = require('./message-body'); 30 | const Messages = require('./messages'); 31 | const MessageLine = require('./message-line'); 32 | const InputFileRef = require('./input-file-ref'); 33 | const FilePath = require('./file-path'); 34 | const ResponseHandler = require('./response-handler'); 35 | const HandlerScript = require('./handler-script'); 36 | const ResponseRef = require('./response-ref'); 37 | const EnvVariable = require('./env-variable'); 38 | 39 | module.exports = { 40 | Literal, 41 | RequestsFile, 42 | Request, 43 | RequestLine, 44 | RequestTarget, 45 | OriginForm, 46 | AbsolutePath, 47 | Query, 48 | Fragment, 49 | PathSeparator, 50 | Segment, 51 | AbsoluteForm, 52 | HierPart, 53 | Authority, 54 | Host, 55 | Port, 56 | Ipv6Address, 57 | Ipv4OrRegName, 58 | AsteriskForm, 59 | Scheme, 60 | Method, 61 | HttpVersion, 62 | Headers, 63 | HeaderField, 64 | FieldName, 65 | FieldValue, 66 | MessageBody, 67 | Messages, 68 | MessageLine, 69 | InputFileRef, 70 | FilePath, 71 | ResponseHandler, 72 | HandlerScript, 73 | ResponseRef, 74 | EnvVariable, 75 | }; 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http-request-in-editor", 3 | "version": "0.4.0", 4 | "description": "Parser for Http Request in Editor Specification", 5 | "main": "./src/index.js", 6 | "type": "commonjs", 7 | "scripts": { 8 | "compile": "nearleyc ./src/parser/grammar.ne --out ./src/parser/grammar.js", 9 | "test": "npm run test:parser && npm run test:runtime", 10 | "test:runtime": "mocha --config test/runtime/.mocharc.js", 11 | "test:parser": "npm run compile && mocha --config test/parser/.mocharc.js", 12 | "test:parser:00000": "npm run compile && cat ./test/parser/fixtures/http/00000.http | nearley-test ./src/parser/grammar.js", 13 | "test:parser:00000:sexprs": "npm run compile && node ./scripts/00000-sexprs.js", 14 | "lint": "eslint ./", 15 | "lint:fix": "eslint ./ --fix", 16 | "corpus:regenerate": "node ./scripts/corpus-regenerate.js", 17 | "railroad": "nearley-railroad ./src/parser/grammar.ne > railroad.html", 18 | "unparse": "nearley-unparse ./src/parser/grammar.js" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/char0n/http-request-in-editor-impl.git" 23 | }, 24 | "keywords": [ 25 | "parser", 26 | "RFC7230", 27 | "http", 28 | "request", 29 | "editor" 30 | ], 31 | "author": { 32 | "name": "Vladimir Gorej", 33 | "email": "vladimir.gorej@gmail.com", 34 | "url": "https://www.linkedin.com/in/vladimirgorej/" 35 | }, 36 | "license": "BSD-3-Clause", 37 | "bugs": { 38 | "url": "https://github.com/char0n/http-request-in-editor-impl/issues" 39 | }, 40 | "homepage": "https://github.com/char0n/http-request-in-editor-impl#readme", 41 | "readme": "README.md", 42 | "dependencies": { 43 | "axios": "=1.6.5", 44 | "nearley": "=2.20.1", 45 | "ramda": "=0.28.0", 46 | "ramda-adjunct": "=3.4.0", 47 | "stampit": "=4.3.2" 48 | }, 49 | "devDependencies": { 50 | "@commitlint/cli": "=17.8.0", 51 | "@commitlint/config-conventional": "=17.8.0", 52 | "chai": "=4.5.0", 53 | "dedent": "=0.7.0", 54 | "eslint": "=8.57.0", 55 | "eslint-config-airbnb-base": "=15.0.0", 56 | "eslint-config-prettier": "=8.10.0", 57 | "eslint-plugin-import": "=2.29.1", 58 | "eslint-plugin-mocha": "=10.2.0", 59 | "eslint-plugin-prettier": "=4.2.1", 60 | "glob": "=8.1.0", 61 | "husky": "=8.0.3", 62 | "lint-staged": "=13.2.3", 63 | "mocha": "=10.2.0", 64 | "prettier": "=2.8.8" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/runtime/runners/axios/AxiosVisitor/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const { assert } = require('chai'); 6 | 7 | const AxiosVisitor = require('../../../../../src/runtime/runners/axios/AxiosVisitor'); 8 | const { parse } = require('../../../../../src/parser'); 9 | const { visit } = require('../../../../../src/visitor'); 10 | 11 | describe('runtime', function () { 12 | context('runners', function () { 13 | context('axios', function () { 14 | context('AxiosVisitor', function () { 15 | context('given single-request.http', function () { 16 | let visitor; 17 | let config; 18 | 19 | beforeEach(async function () { 20 | const httpFilePath = path.join( 21 | __dirname, 22 | 'fixtures', 23 | 'single-request.http' 24 | ); 25 | const httpFileContent = fs.readFileSync(httpFilePath).toString(); 26 | const cstTree = parse(httpFileContent); 27 | 28 | visitor = AxiosVisitor(); 29 | visit(cstTree, visitor); 30 | [config] = visitor.configs; 31 | }); 32 | 33 | specify( 34 | 'should construct single axios Request Config object', 35 | function () { 36 | assert.lengthOf(visitor.configs, 1); 37 | } 38 | ); 39 | 40 | specify('should construct proper method', function () { 41 | assert.strictEqual(config.method, 'post'); 42 | }); 43 | 44 | specify('should construct proper url', function () { 45 | assert.strictEqual(config.url, '/post'); 46 | }); 47 | 48 | specify('should construct proper baseURL', function () { 49 | assert.strictEqual(config.baseURL, 'https://httpbin.org/'); 50 | }); 51 | 52 | specify('should construct proper params', function () { 53 | assert.instanceOf(config.params, URLSearchParams); 54 | assert.strictEqual(config.params.get('search1'), 'value1'); 55 | assert.strictEqual(config.params.get('search2'), 'value2'); 56 | }); 57 | 58 | specify('should construct proper headers', function () { 59 | assert.strictEqual( 60 | config.headers['Content-Type'], 61 | 'application/json' 62 | ); 63 | }); 64 | 65 | specify('should construct proper data', function () { 66 | assert.strictEqual(config.data, '{}'); 67 | }); 68 | }); 69 | }); 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '44 11 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | with: 74 | category: "/language:${{matrix.language}}" 75 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at vladimir.gorej@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Node CI](https://github.com/char0n/http-request-in-editor/workflows/Node.js%20CI/badge.svg)](https://github.com/char0n/http-request-in-editor/actions?query=workflow%3A%22Node.js+CI%22) 2 | 3 | # HTTP Request in Editor Implementation 4 | 5 | ## What is this? 6 | 7 | [JetBrains company](https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html) has come up with 8 | with very interesting concept called [HTTP Request in Editor Spec](https://github.com/JetBrains/http-request-in-editor-spec/blob/master/spec.md). 9 | They implemented this spec into their [IDEs](https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html). 10 | You are able to create files with `.http` extensions with nice syntax highlighting and run HTTP requests directly in your editor. 11 | Eventually you can create collection of these `.http` files, check them in as part of your codebase and 12 | share them with the rest of your team. This is really great...but... 13 | 14 |

15 | 16 | There is currently no [CLI](https://en.wikipedia.org/wiki/Command-line_interface) runner for `.http` files. From the 17 | moment we checked our `.http` files inside our project we should test if those file reflect reality. 18 | This project provides tool for running `.http` files on your [CI](https://en.wikipedia.org/wiki/Continuous_integration). 19 | 20 | **Warning:** Currently we have only implemented the parser which creates documented CST. We're working hard on implementation 21 | of runner compatible with JetBrains one. Stay tuned for the runner! 22 | 23 |
24 | 25 | This repo contains reference implementation of [HTTP Request in Editor Spec](https://github.com/JetBrains/http-request-in-editor-spec/blob/master/spec.md) parser. 26 | 27 | The [HTTP Request in Editor Spec](https://github.com/JetBrains/http-request-in-editor-spec/blob/master/spec.md) is using context-free grammar to present set of production rules. 28 | We're using [nearley](https://nearley.js.org/) to declaratively map this grammar and generate a JavaScript parser from it. 29 | 30 | Parser can parse following syntax and creates [CST](https://en.wikipedia.org/wiki/Parse_tree) 31 | that can be consumed and executed by various runtime environments. I'll provide a reference implementation 32 | of a reference runtime implementation as soon as the parser is finished. 33 | 34 | ``` 35 | ### request 1 36 | POST https://httpbin.org/post HTTP/1.1 37 | Authorization: token 38 | 39 | request body 1 40 | 41 | ### request 2 42 | POST https://httpbin.org/post HTTP/2.0 43 | Authorization: token2 44 | 45 | { 46 | "test": 3, 47 | "a": "b" 48 | } 49 | 50 | ### request 3 51 | POST https://httpbin.org/post HTTP/3.0 52 | Authorization: token3 53 | 54 | {} 55 | 56 | ### 57 | ``` 58 | 59 | ## Installation 60 | 61 | ```sh 62 | $ npm i http-request-in-editor 63 | ``` 64 | 65 | ## Parser 66 | 67 | ```js 68 | const { parse } = require('http-request-in-editor'); 69 | 70 | 71 | const http = ` 72 | POST https://httpbin.org/post 73 | 74 | ### 75 | `; 76 | 77 | const cst = parse(http); 78 | // now process the CST with your favorite http library 79 | ``` 80 | 81 | 82 | ### CST 83 | 84 | Parser is producing JSON serializable [CST](https://en.wikipedia.org/wiki/Parse_tree). Following [HTTP Request in Editor](https://github.com/JetBrains/http-request-in-editor-spec/blob/master/spec.md) fragment 85 | 86 | ``` 87 | ### post request 88 | POST http://www.example.com HTTP/2.0 89 | # comment 90 | Authorization: token 91 | 92 | message body 93 | 94 | > {% script %} 95 | <> ./file.json 96 | 97 | ### 98 | ``` 99 | 100 | will produce following CST: 101 | 102 | ``` 103 | (requests-file 104 | (request 105 | (requests-line 106 | (method) 107 | (request-target 108 | (absolute-form 109 | (scheme) 110 | (literal) 111 | (hier-part 112 | (authority 113 | (host 114 | (ipv4-or-reg-name)))))) 115 | (http-version)) 116 | (headers 117 | (header-field 118 | (field-name) 119 | (field-value))) 120 | (message-body 121 | (messages 122 | (message-line) 123 | (message-line))) 124 | (response-handler 125 | (handler-script)) 126 | (response-ref 127 | (file-path)))) 128 | ``` 129 | 130 | CST should be self-explanatory. For more information checkout our [CST Types](https://github.com/char0n/http-request-in-editor/tree/master/src/parser/cst). 131 | 132 | ## Runner 133 | 134 | ```js 135 | const fs = require('fs'); 136 | const { runSpec } = require('http-request-in-editor'); 137 | 138 | const spec = fs.readFileSync('./spec-file.http'); 139 | const result = await runSpec(spec); // returns list of response objects 140 | ``` 141 | 142 | ## Development 143 | 144 | Fork the repo, clone in and install by 145 | 146 | ```sh 147 | $ npm i 148 | ``` 149 | 150 | Edit `src/parser/grammer.ne` and create a grammar that maps to HTTP Request in Editor Spec. 151 | 152 | Compiles `src/parser/grammar.ne` file into `src/parser/grammar.js`. 153 | ```sh 154 | $ npm run compile 155 | ``` 156 | 157 | Test parser grammar against predefined fixtures. 158 | ```sh 159 | $ npm test 160 | ``` 161 | 162 | Generate railroad diagrams from `src/parser/grammar.ne` file. 163 | ```sh 164 | $ npm run railroad 165 | ``` 166 | 167 | Generate random strings that satisfy the grammar defined in`src/parser/grammar.ne` file. 168 | ```sh 169 | $ npm run unparse 170 | ``` 171 | 172 | Regenerates [Corpus file](https://github.com/char0n/http-request-in-editor/tree/master/test/corpus/corpus.txt). Replaces CST S-expression representation with the new one. 173 | ```sh 174 | $ npm run corpus:regenerate 175 | ``` 176 | 177 | Lint the source code. 178 | ```sh 179 | $ npm run lint 180 | ``` 181 | 182 | ## Notes 183 | 184 | 185 | 1. `Multipart-form-data` will not be part of resulting CST. It's just a special shape of `message-body` and requires 186 | no special handling even during runtime. 187 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to http-request-in-editor 2 | 3 | As a contributor, here are the guidelines we would like you to follow: 4 | 5 | - [Submission Guidelines](#submit) 6 | - [Coding Rules](#rules) 7 | - [Commit Message Guidelines](#commit) 8 | 9 | ## Submission Guidelines 10 | 11 | ### Submitting a Pull Request (PR) 12 | Before you submit your Pull Request (PR) consider the following guidelines: 13 | 14 | * Make your self familiar with [git rebase workflow][git-rebase-workflow] 15 | * Make your changes in a new git branch: 16 | 17 | ```shell 18 | git checkout -b my-fix-branch master 19 | ``` 20 | 21 | * Create your patch, **including appropriate test cases**. 22 | * Follow our [Coding Rules](#rules). 23 | * Run the full test suite, as described in the developer documentation, 24 | and ensure that all tests pass. 25 | * Commit your changes using a descriptive commit message that follows our 26 | [commit message conventions](#commit). Adherence to these conventions 27 | is necessary because release notes are automatically generated from these messages. 28 | 29 | ```shell 30 | git commit -a 31 | ``` 32 | Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files. 33 | 34 | * Push your branch to GitHub: 35 | 36 | ```shell 37 | git push origin my-fix-branch 38 | ``` 39 | 40 | * In GitHub, send a pull request to `.npmrc:master`. 41 | * If we suggest changes then: 42 | * Make the required updates. 43 | * Re-run the test suites to ensure tests are still passing. 44 | * Rebase your branch and force push to your GitHub repository (this will update your Pull Request): 45 | 46 | ```shell 47 | git rebase master -i 48 | git push -f 49 | ``` 50 | 51 | That's it! 52 | 53 | #### After your pull request is merged 54 | 55 | After your pull request is merged, you can safely delete your branch and pull the changes 56 | from the main (upstream) repository: 57 | 58 | * Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows: 59 | 60 | ```shell 61 | git push origin --delete my-fix-branch 62 | ``` 63 | 64 | * Check out the master branch: 65 | 66 | ```shell 67 | git checkout master -f 68 | ``` 69 | 70 | * Delete the local branch: 71 | 72 | ```shell 73 | git branch -D my-fix-branch 74 | ``` 75 | 76 | * Update your master with the latest upstream version: 77 | 78 | ```shell 79 | git pull 80 | ``` 81 | 82 | ## Coding Rules 83 | To ensure consistency throughout the source code, keep these rules in mind as you are working: 84 | 85 | * All features or bug fixes **must be tested** by one or more specs (unit-tests). 86 | * Every cryptic code block **must be documented**. 87 | * We follow [Airbnb JavaScript Style Guide][js-style-guide], but wrap all code at 88 | **120 characters**. We use eslint to enforce the codestyle. 89 | 90 | ## Commit Message Guidelines 91 | 92 | We have very precise rules over how our git commit messages can be formatted. This leads to **more 93 | readable messages** that are easy to follow when looking through the **project history**. But also, 94 | we use the git commit messages to **generate the http-request-in-editor change log**. 95 | 96 | ### Commit Message Format 97 | Each commit message consists of a **header**, a **body** and a **footer**. The header has a special 98 | format that includes a **type**, a **scope** and a **subject**: 99 | 100 | ``` 101 | (): 102 | 103 | 104 | 105 |