Skip to content

Commit 1d6f8a4

Browse files
committed
Add native .env file support #418
1 parent 301b0a3 commit 1d6f8a4

File tree

7 files changed

+66
-12
lines changed

7 files changed

+66
-12
lines changed

README.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,15 @@ REST Client allows you to send HTTP request and view the response in Visual Stud
2727
- Diagnostic support for __request__ and __file__ custom variables
2828
- Go to definition support for __request__ and __file__ custom variables
2929
- Find all references support _ONLY_ for __file__ custom variables
30-
- Provide system dynamic variables `{{$guid}}`, `{{$randomInt min max}}`, `{{$timestamp [offset option]}}`, `{{$datetime rfc1123|iso8601 [offset option]}}`, `{{$processEnv [%]envVarName}}`, `{{$localDatetime rfc1123|iso8601 [offset option]}}`, `{{$processEnv [%]envVarName}}`, and `{{$aadToken [new] [public|cn|de|us|ppe] [<domain|tenantId>] [aud:<domain|tenantId>]}}`
30+
- Provide system dynamic variables
31+
+ `{{$guid}}`
32+
+ `{{$randomInt min max}}`
33+
+ `{{$timestamp [offset option]}}`
34+
+ `{{$datetime rfc1123|iso8601 [offset option]}}`
35+
+ `{{$localDatetime rfc1123|iso8601 [offset option]}}`
36+
+ `{{$processEnv [%]envVarName}}`
37+
+ `{{$dotenv variableName}}`
38+
+ `{{$aadToken [new] [public|cn|de|us|ppe] [<domain|tenantId>] [aud:<domain|tenantId>]}}`
3139
- Easily create/update/delete environments and environment variables in setting file
3240
- File variables can reference both custom and system variables
3341
- Support environment switch
@@ -537,6 +545,7 @@ For example: Define a shell environment variable in `.bashrc` or similar on wind
537545
`%`: Optional. If specified, treats envVarName as an extension setting environment variable, and uses the value of that for the lookup.
538546

539547

548+
* `{{$dotenv variableName}}`: Returns the environment value stored in the [`.env`](https://github.com/motdotla/dotenv) file which exists in the same directory of your `.http` file.
540549
* `{{$randomInt min max}}`: Returns a random integer between min (included) and max (excluded)
541550
* `{{$timestamp [offset option]}}`: Add UTC timestamp of now. You can even specify any date time based on current time in the format `{{$timestamp number option}}`, e.g., to represent 3 hours ago, simply `{{$timestamp -3 h}}`; to represent the day after tomorrow, simply `{{$timestamp 2 d}}`.
542551
* `{{$datetime rfc1123|iso8601|"custom format"|'custom format' [offset option]}}`: Add a datetime string in either _ISO8601_, _RFC1123_ or a custom display format. You can even specify a date time relative to the current date similar to `timestamp` like: `{{$datetime iso8601 1 y}}` to represent a year later in _ISO8601_ format. If specifying a custom format, wrap it in single or double quotes like: `{{$datetime "DD-MM-YYYY" 1 y}}`. The date is formatted using moment.js, read [here](https://momentjs.com/docs/#/parsing/string-format/) for information on format strings.
@@ -563,6 +572,7 @@ Content-Type: application/xml
563572
Date: {{$datetime rfc1123}}
564573
565574
{
575+
"user_name": "{{$dotenv USERNAME}}",
566576
"request_id": "{{$guid}}",
567577
"updated_at": "{{$timestamp}}",
568578
"created_at": "{{$timestamp -1 d}}",

package-lock.json

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,7 @@
633633
"applicationinsights": "^1.0.5",
634634
"code-highlight-linenums": "^0.2.1",
635635
"combined-stream": "^1.0.5",
636+
"dotenv": "^8.2.0",
636637
"encodeurl": "^1.0.1",
637638
"filesize": "^3.3.0",
638639
"fs-extra": "^5.0.0",

src/common/constants.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ export const RandomIntVariableName = "$randomInt";
3131
export const RandomIntDescription = "Returns a random integer between min (included) and max (excluded)";
3232
export const ProcessEnvVariableName = "$processEnv";
3333
export const ProcessEnvDescription = "Returns the value of process environment variable or '' if not found ";
34-
34+
export const DotenvVariableName = "$dotenv";
35+
export const DotenvDescription = "Returns the environment value stored in a .env file";
3536
export const AzureActiveDirectoryVariableName = "$aadToken";
3637
export const AzureActiveDirectoryDescription = "Prompts to sign in to Azure AD and adds the token to the request";
3738
/**

src/models/httpVariableResolveResult.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,16 @@ export const enum ResolveWarningMessage {
2929
ResponseBodyNotExist = "Response body of given request doesn't exist",
3030
IncorrectDateTimeVariableFormat = 'Datetime system variable should follow format "{{$datetime rfc1123|iso8601 [integer y|Q|M|w|d|h|m|s|ms]}}"',
3131
IncorrectLocalDateTimeVariableFormat = 'Local datetime system variable should follow format "{{$localDatetime rfc1123|iso8601 [integer y|Q|M|w|d|h|m|s|ms]}}"',
32+
DotenvFileNotFound = '.env file is not found in the directory where current .http file exists',
33+
DotenvVariableNotFound = 'Given variable name is not found in .env file',
3234
IncorrectHeaderName = 'No value is resolved for given header name',
3335
IncorrectJSONPath = 'No value is resolved for given JSONPath',
3436
IncorrectRandomIntegerVariableFormat = 'RandomInt system variable should follow format "{{$randomInt minInteger maxInteger}}"',
3537
IncorrectProcessEnvVariableFormat = 'ProcessEnv system variable should follow format "{{$processEnv envVarName}}"',
3638
IncorrectTimestampVariableFormat = 'Timestamp system variable should follow format "{{$timestamp [integer y|Q|M|w|d|h|m|s|ms]}}"',
39+
IncorrectDotenvVariableFormat = 'Dotenv variable should follow format "{{$dotenv variableName}}"',
3740
IncorrectXPath = 'No value is resolved for given XPath',
38-
UnsupportedBodyContentType = 'Only JSON response/request body is supported to query the result',
41+
UnsupportedBodyContentType = 'Only JSON and XML response/request body is supported to query the result',
3942
InvalidJSONPath = 'Invalid JSONPath query',
4043
InvalidXPath = 'Invalid XPath query',
4144
}

src/utils/httpElementFactory.ts

+7
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ export class HttpElementFactory {
124124
Constants.ProcessEnvDescription,
125125
new SnippetString(`{{$\${name:${Constants.ProcessEnvVariableName.slice(1)}} \${2:process environment variable name}}}`)
126126
));
127+
originalElements.push(new HttpElement(
128+
Constants.DotenvVariableName,
129+
ElementType.SystemVariable,
130+
null,
131+
Constants.DotenvDescription,
132+
new SnippetString(`{{$\${name:${Constants.DotenvVariableName.slice(1)}} \${2:.env variable name}}}`)
133+
));
127134
originalElements.push(new HttpElement(
128135
Constants.AzureActiveDirectoryVariableName,
129136
ElementType.SystemVariable,

src/utils/httpVariableProviders/systemVariableProvider.ts

+36-9
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
'use strict';
22

33
import * as adal from 'adal-node';
4+
import * as dotenv from 'dotenv';
5+
import * as fs from 'fs-extra';
46
import { DurationInputArg2, Moment, utc } from 'moment';
5-
import { Clipboard, commands, env, QuickPickItem, QuickPickOptions, Uri, window } from 'vscode';
7+
import * as path from 'path';
8+
import { Clipboard, commands, env, QuickPickItem, QuickPickOptions, TextDocument, Uri, window } from 'vscode';
69
import * as Constants from '../../common/constants';
710
import { HttpRequest } from '../../models/httpRequest';
811
import { ResolveErrorMessage, ResolveWarningMessage } from '../../models/httpVariableResolveResult';
@@ -15,7 +18,7 @@ import { HttpVariable, HttpVariableContext, HttpVariableProvider } from './httpV
1518
const uuidv4 = require('uuid/v4');
1619

1720
type SystemVariableValue = Pick<HttpVariable, Exclude<keyof HttpVariable, 'name'>>;
18-
type ResolveSystemVariableFunc = (name: string, context: HttpVariableContext) => Promise<SystemVariableValue>;
21+
type ResolveSystemVariableFunc = (name: string, document: TextDocument, context: HttpVariableContext) => Promise<SystemVariableValue>;
1922

2023
export class SystemVariableProvider implements HttpVariableProvider {
2124

@@ -27,6 +30,8 @@ export class SystemVariableProvider implements HttpVariableProvider {
2730
private readonly randomIntegerRegex: RegExp = new RegExp(`\\${Constants.RandomIntVariableName}\\s(\\-?\\d+)\\s(\\-?\\d+)`);
2831
private readonly processEnvRegex: RegExp = new RegExp(`\\${Constants.ProcessEnvVariableName}\\s(\\%)?(\\w+)`);
2932

33+
private readonly dotenvRegex: RegExp = new RegExp(`\\${Constants.DotenvVariableName}\\s([\\w-.]+)`);
34+
3035
private readonly requestUrlRegex: RegExp = /^(?:[^\s]+\s+)([^:]*:\/\/\/?[^/\s]*\/?)/;
3136

3237
private readonly aadRegex: RegExp = new RegExp(`\\s*\\${Constants.AzureActiveDirectoryVariableName}(\\s+(${Constants.AzureActiveDirectoryForceNewOption}))?(\\s+(ppe|public|cn|de|us))?(\\s+([^\\.]+\\.[^\\}\\s]+|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}))?(\\s+aud:([^\\.]+\\.[^\\}\\s]+|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}))?\\s*`);
@@ -50,23 +55,24 @@ export class SystemVariableProvider implements HttpVariableProvider {
5055
this.registerGuidVariable();
5156
this.registerRandomIntVariable();
5257
this.registerProcessEnvVariable();
58+
this.registerDotenvVariable();
5359
this.registerAadTokenVariable();
5460
}
5561

5662
public readonly type: VariableType = VariableType.System;
5763

58-
public async has(name: string): Promise<boolean> {
64+
public async has(name: string, document: TextDocument): Promise<boolean> {
5965
const [variableName] = name.split(' ').filter(Boolean);
6066
return this.resolveFuncs.has(variableName);
6167
}
6268

63-
public async get(name: string, document: undefined, context: HttpVariableContext): Promise<HttpVariable> {
69+
public async get(name: string, document: TextDocument, context: HttpVariableContext): Promise<HttpVariable> {
6470
const [variableName] = name.split(' ').filter(Boolean);
6571
if (!this.resolveFuncs.has(variableName)) {
6672
return { name: variableName, error: ResolveErrorMessage.SystemVariableNotExist };
6773
}
6874

69-
const result = await this.resolveFuncs.get(variableName)!(name, context);
75+
const result = await this.resolveFuncs.get(variableName)!(name, document, context);
7076
return { name: variableName, ...result };
7177
}
7278

@@ -138,7 +144,7 @@ export class SystemVariableProvider implements HttpVariableProvider {
138144
}
139145

140146
private registerGuidVariable() {
141-
this.resolveFuncs.set(Constants.GuidVariableName, async name => ({ value: uuidv4() }));
147+
this.resolveFuncs.set(Constants.GuidVariableName, async () => ({ value: uuidv4() }));
142148
}
143149

144150
private registerRandomIntVariable() {
@@ -181,17 +187,38 @@ export class SystemVariableProvider implements HttpVariableProvider {
181187
}
182188
const envValue = process.env[processEnvName];
183189
if (envValue !== undefined) {
184-
return { value: envValue.toString()};
190+
return { value: envValue.toString() };
185191
} else {
186-
return { value: ''};
192+
return { value: '' };
187193
}
188194
}
189195
return { warning: ResolveWarningMessage.IncorrectProcessEnvVariableFormat };
190196
});
191197
}
192198

199+
private registerDotenvVariable() {
200+
this.resolveFuncs.set(Constants.DotenvVariableName, async (name, document) => {
201+
const absolutePath = path.join(path.dirname(document.fileName), '.env');
202+
if (!await fs.pathExists(absolutePath)) {
203+
return { warning: ResolveWarningMessage.DotenvFileNotFound };
204+
}
205+
const parsed = dotenv.parse(fs.readFileSync(absolutePath));
206+
const groups = this.dotenvRegex.exec(name);
207+
if (groups !== null && groups.length === 2) {
208+
const [, key] = groups;
209+
if (!(key in parsed)) {
210+
return { warning: ResolveWarningMessage.DotenvVariableNotFound };
211+
}
212+
213+
return { value: parsed[key] };
214+
}
215+
216+
return { warning: ResolveWarningMessage.IncorrectDotenvVariableFormat };
217+
});
218+
}
219+
193220
private registerAadTokenVariable() {
194-
this.resolveFuncs.set(Constants.AzureActiveDirectoryVariableName, (name, context) => {
221+
this.resolveFuncs.set(Constants.AzureActiveDirectoryVariableName, (name, document, context) => {
195222
// get target app from URL
196223
const match = this.requestUrlRegex.exec(context.parsedRequest);
197224
const url = (match && match[1]) || context.parsedRequest;

0 commit comments

Comments
 (0)