Skip to content

Commit fa73d2d

Browse files
authored
Merge pull request #97 from WeihanLi/dev
0.11.0
2 parents 1d34aa7 + 98e0f09 commit fa73d2d

35 files changed

+548
-239
lines changed

Directory.Packages.props

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@
44
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
55
</PropertyGroup>
66
<ItemGroup>
7-
<PackageVersion Include="JsonPath.Net" Version="1.1.6" />
7+
<PackageVersion Include="JsonPath.Net" Version="2.1.1" />
88
<PackageVersion Include="JsonSchema.Net" Version="7.3.4" />
99
<PackageVersion Include="MathNet.Numerics.Signed" Version="5.0.0" />
10-
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
11-
<PackageVersion Include="WeihanLi.Common" Version="1.0.78" />
10+
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta5.25306.1" />
11+
<PackageVersion Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net10.0'))" Include="System.Linq.AsyncEnumerable" Version="10.0.0-preview.6.25358.103" />
12+
<PackageVersion Include="WeihanLi.Common" Version="1.0.80" />
1213
</ItemGroup>
1314
<ItemGroup>
1415
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
15-
<PackageVersion Include="FluentAssertions" Version="6.12.2" />
16+
<PackageVersion Include="AwesomeAssertions" Version="7.0.0" />
1617
<PackageVersion Include="Moq" Version="4.20.72" />
1718
<PackageVersion Include="xunit" Version="2.9.3" />
1819
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
1920
<PackageVersion Include="Xunit.DependencyInjection" Version="9.9.1" />
2021
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
2122
</ItemGroup>
22-
</Project>
23+
</Project>

build/version.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22
<PropertyGroup>
33
<VersionMajor>0</VersionMajor>
4-
<VersionMinor>10</VersionMinor>
4+
<VersionMinor>11</VersionMinor>
55
<VersionPatch>0</VersionPatch>
66
<VersionPrefix>$(VersionMajor).$(VersionMinor).$(VersionPatch)</VersionPrefix>
77
<InformationalVersion>$(PackageVersion)</InformationalVersion>

docs/ReleaseNotes.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# dotnet-httpie release notes
22

3+
## Unreleased
4+
5+
- upgrade System.CommandLine
6+
37
## [0.10.0](https://nuget.org/packages/dotnet-httpie/0.10.0)
48

59
- Add http env file support for http file execution

src/HTTPie/Abstractions/IHttpParser.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ public interface IHttpParser
99
{
1010
string? Environment { get; set; }
1111

12-
Task<HttpRequestMessage> ParseScriptAsync(string script, CancellationToken cancellationToken = default);
12+
IAsyncEnumerable<HttpRequestMessageWrapper> ParseScriptAsync(string script,
13+
CancellationToken cancellationToken = default);
1314

1415
IAsyncEnumerable<HttpRequestMessageWrapper> ParseFileAsync(string filePath,
1516
CancellationToken cancellationToken = default);

src/HTTPie/Commands/ExecuteCommand.cs

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
// Licensed under the MIT license.
33

44
using HTTPie.Abstractions;
5+
using HTTPie.Implement;
6+
using HTTPie.Middleware;
57
using HTTPie.Utilities;
68
using Json.Path;
79
using Microsoft.Extensions.DependencyInjection;
810
using Microsoft.Extensions.Logging;
9-
using System.CommandLine.Invocation;
1011
using System.Diagnostics;
1112
using System.Text;
1213
using System.Text.Json.Nodes;
@@ -17,58 +18,101 @@ namespace HTTPie.Commands;
1718

1819
public sealed class ExecuteCommand : Command
1920
{
20-
private static readonly Argument<string> FilePathArgument = new("scriptPath", "The script to execute");
21+
private static readonly Argument<string> FilePathArgument = new("scriptPath")
22+
{
23+
Description = "The script to execute",
24+
Arity = ArgumentArity.ZeroOrOne
25+
};
2126

2227
private static readonly Option<string> EnvironmentTypeOption =
23-
new(["--env"], "The environment to execute script");
28+
new("--env")
29+
{
30+
Description = "The environment to execute script"
31+
};
2432

2533
private static readonly Option<ExecuteScriptType> ExecuteScriptTypeOption =
26-
new(["-t", "--type"], "The script type to execute");
34+
new("-t", "--type")
35+
{
36+
Description = "The script type to execute"
37+
};
2738

2839
public ExecuteCommand() : base("exec", "execute http request")
2940
{
30-
AddOption(ExecuteScriptTypeOption);
31-
AddOption(EnvironmentTypeOption);
32-
AddArgument(FilePathArgument);
41+
Options.Add(ExecuteScriptTypeOption);
42+
Options.Add(EnvironmentTypeOption);
43+
Options.Add(DefaultRequestMiddleware.DebugOption);
44+
Options.Add(OutputFormatter.OfflineOption);
45+
Arguments.Add(FilePathArgument);
3346
}
3447

35-
public async Task InvokeAsync(InvocationContext invocationContext, IServiceProvider serviceProvider)
48+
public async Task InvokeAsync(
49+
ParseResult parseResult, CancellationToken cancellationToken, IServiceProvider serviceProvider
50+
)
3651
{
37-
var filePath = invocationContext.ParseResult.GetValueForArgument(FilePathArgument);
38-
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
52+
var scriptText = string.Empty;
53+
var filePath = parseResult.GetValue(FilePathArgument);
54+
if (string.IsNullOrEmpty(filePath))
55+
{
56+
// try to read script content from stdin
57+
if (ConsoleHelper.HasStandardInput())
58+
{
59+
scriptText = (await Console.In.ReadToEndAsync(cancellationToken)).Trim();
60+
}
61+
62+
if (string.IsNullOrEmpty(scriptText))
63+
{
64+
throw new InvalidOperationException("Invalid script to execute");
65+
}
66+
}
67+
else
3968
{
40-
throw new InvalidOperationException("Invalid filePath");
69+
if (!File.Exists(filePath))
70+
{
71+
throw new InvalidOperationException($"Invalid filePath {filePath}");
72+
}
4173
}
4274

4375
var logger = serviceProvider.GetRequiredService<ILogger>();
4476
var requestExecutor = serviceProvider.GetRequiredService<IRawHttpRequestExecutor>();
45-
var cancellationToken = invocationContext.GetCancellationToken();
46-
var type = invocationContext.ParseResult.GetValueForOption(ExecuteScriptTypeOption);
47-
var environment = invocationContext.ParseResult.GetValueForOption(EnvironmentTypeOption);
77+
var type = parseResult.GetValue(ExecuteScriptTypeOption);
78+
var environment = parseResult.GetValue(EnvironmentTypeOption);
4879
var parser = type switch
4980
{
5081
ExecuteScriptType.Http => serviceProvider.GetRequiredService<IHttpParser>(),
5182
ExecuteScriptType.Curl => serviceProvider.GetRequiredService<ICurlParser>(),
5283
_ => throw new InvalidOperationException($"Not supported request type: {type}")
5384
};
5485
parser.Environment = environment;
55-
logger.LogDebug("Executing {ScriptType} http request {ScriptPath} with {ScriptExecutor}",
56-
type, filePath, parser.GetType().Name);
57-
await InvokeRequest(parser, requestExecutor, filePath, cancellationToken);
86+
logger.LogDebug(
87+
"Executing {ScriptType} http request with scriptPath: [{ScriptPath}], scriptText: ({ScriptText}), environment: {Environment} via {ScriptExecutor}",
88+
type, filePath, scriptText, environment, parser.GetType().Name
89+
);
90+
91+
var offline = parseResult.GetValue(OutputFormatter.OfflineOption);
92+
await InvokeRequest(parser, requestExecutor, scriptText, filePath, offline, cancellationToken);
5893
}
5994

60-
private static async Task InvokeRequest(IHttpParser httpParser, IRawHttpRequestExecutor requestExecutor,
61-
string filePath, CancellationToken cancellationToken)
95+
private static async Task InvokeRequest(
96+
IHttpParser httpParser, IRawHttpRequestExecutor requestExecutor, string scriptText,
97+
string? filePath, bool offline, CancellationToken cancellationToken)
6298
{
6399
var responseList = new Dictionary<string, HttpResponseMessage>();
100+
64101
try
65102
{
66-
await foreach (var request in httpParser.ParseFileAsync(filePath, cancellationToken))
103+
var getRequests = string.IsNullOrEmpty(filePath)
104+
? httpParser.ParseScriptAsync(scriptText, cancellationToken)
105+
: httpParser.ParseFileAsync(filePath, cancellationToken)
106+
;
107+
await foreach (var request in getRequests.WithCancellation(cancellationToken))
67108
{
68109
await EnsureRequestVariableReferenceReplaced(request, responseList);
69110
var response = await ExecuteRequest(
70-
requestExecutor, request.RequestMessage, cancellationToken, request.Name
111+
requestExecutor, request.RequestMessage, offline, cancellationToken, request.Name
71112
);
113+
if (response is null)
114+
continue;
115+
72116
responseList[request.Name] = response;
73117
}
74118
}
@@ -90,9 +134,10 @@ private static async Task InvokeRequest(IHttpParser httpParser, IRawHttpRequestE
90134
}
91135
}
92136

93-
private static async Task<HttpResponseMessage> ExecuteRequest(
137+
private static async Task<HttpResponseMessage?> ExecuteRequest(
94138
IRawHttpRequestExecutor requestExecutor,
95139
HttpRequestMessage requestMessage,
140+
bool offline,
96141
CancellationToken cancellationToken,
97142
string? requestName = null)
98143
{
@@ -101,6 +146,9 @@ private static async Task<HttpResponseMessage> ExecuteRequest(
101146

102147
Console.WriteLine("Request message:");
103148
Console.WriteLine(await requestMessage.ToRawMessageAsync(cancellationToken));
149+
if (offline)
150+
return null;
151+
104152
var startTimestamp = Stopwatch.GetTimestamp();
105153
var response = await requestExecutor.ExecuteAsync(requestMessage, cancellationToken);
106154
var requestDuration = ProfilerHelper.GetElapsedTime(startTimestamp);

src/HTTPie/HTTPie.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<PackageReference Include="JsonSchema.Net" />
4444
<PackageReference Include="MathNet.Numerics.Signed" />
4545
<PackageReference Include="System.CommandLine" />
46+
<PackageReference Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net10.0'))" Include="System.Linq.AsyncEnumerable" />
4647
<PackageReference Include="WeihanLi.Common" />
4748
</ItemGroup>
4849
</Project>

src/HTTPie/Implement/CurlParser.cs

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,60 @@
55
using HTTPie.Models;
66
using HTTPie.Utilities;
77
using System.Runtime.CompilerServices;
8+
using CommandLineParser = WeihanLi.Common.Helpers.CommandLineParser;
89

910
namespace HTTPie.Implement;
1011

11-
public sealed class CurlParser : ICurlParser
12+
public sealed class CurlParser : AbstractHttpRequestParser, ICurlParser
1213
{
13-
public string? Environment { get; set; }
14+
public override async IAsyncEnumerable<HttpRequestMessageWrapper> ParseScriptAsync
15+
(string script, [EnumeratorCancellation] CancellationToken cancellationToken = default)
16+
{
17+
Guard.NotNullOrEmpty(script);
18+
await foreach (var request in ParseHttpRequestsAsync(
19+
script.Split("\n###\n").ToAsyncEnumerable(), null,
20+
cancellationToken))
21+
{
22+
yield return request;
23+
}
24+
}
1425

15-
public Task<HttpRequestMessage> ParseScriptAsync(string curlScript, CancellationToken cancellationToken = default)
26+
public override async IAsyncEnumerable<HttpRequestMessageWrapper> ParseFileAsync(
27+
string filePath, [EnumeratorCancellation] CancellationToken cancellationToken = default)
28+
{
29+
Guard.NotNullOrEmpty(filePath);
30+
var script = await File.ReadAllTextAsync(filePath, cancellationToken);
31+
await foreach (var request in ParseHttpRequestsAsync(
32+
script.Split("\n###\n").ToAsyncEnumerable(), filePath,
33+
cancellationToken))
34+
{
35+
yield return request;
36+
}
37+
}
38+
39+
protected override async IAsyncEnumerable<HttpRequestMessageWrapper> ParseHttpRequestsAsync
40+
(
41+
IAsyncEnumerable<string> chunks,
42+
string? filePath,
43+
[EnumeratorCancellation] CancellationToken cancellationToken
44+
)
45+
{
46+
var index = 0;
47+
await foreach (var chunk in chunks.WithCancellation(cancellationToken))
48+
{
49+
var request = ParseCurlScript(chunk, cancellationToken);
50+
var requestName = $"request#{index}";
51+
index++;
52+
yield return new HttpRequestMessageWrapper(requestName, request);
53+
}
54+
}
55+
56+
private static HttpRequestMessage ParseCurlScript(
57+
string curlScript, CancellationToken cancellationToken = default
58+
)
1659
{
1760
Guard.NotNullOrEmpty(curlScript);
61+
cancellationToken.ThrowIfCancellationRequested();
1862
var normalizedScript = curlScript
1963
.Replace("\\\n", " ")
2064
.Replace("\\\r\n", " ")
@@ -26,7 +70,9 @@ public Task<HttpRequestMessage> ParseScriptAsync(string curlScript, Cancellation
2670
throw new ArgumentException($"Invalid curl script: {curlScript}", nameof(curlScript));
2771
}
2872

29-
var splits = CommandLineParser.ParseLine(normalizedScript).ToArray();
73+
var splits = CommandLineParser.ParseLine(normalizedScript)
74+
.Where(s => !string.IsNullOrEmpty(s))
75+
.ToArray();
3076
string requestMethod = string.Empty, requestBody = string.Empty;
3177
Uri? uri = null;
3278
var headers = new List<KeyValuePair<string, string>>();
@@ -76,15 +122,20 @@ public Task<HttpRequestMessage> ParseScriptAsync(string curlScript, Cancellation
76122
{
77123
var header = splits[i].Trim('\'', '"');
78124
var headerSplits = header.Split(':', 2, StringSplitOptions.TrimEntries);
79-
headers.Add(new KeyValuePair<string, string>(headerSplits[0],
80-
headerSplits.Length > 1 ? headerSplits[1] : string.Empty));
125+
if (headerSplits.Length == 2)
126+
{
127+
headers.Add(new KeyValuePair<string, string>(headerSplits[0], headerSplits[1].Trim()));
128+
}
81129
}
82130
}
83131
}
84132

85-
if (string.IsNullOrEmpty(requestMethod)) requestMethod = "GET";
133+
if (string.IsNullOrEmpty(requestMethod))
134+
{
135+
requestMethod = requestBody.IsNullOrEmpty() ? HttpMethod.Get.Method : HttpMethod.Post.Method;
136+
}
86137

87-
if (uri is null) throw new ArgumentException("Url info not found");
138+
if (uri is null) throw new ArgumentException("Request url info not found");
88139

89140
var request = new HttpRequestMessage(new HttpMethod(requestMethod), uri);
90141
// request body
@@ -98,23 +149,10 @@ public Task<HttpRequestMessage> ParseScriptAsync(string curlScript, Cancellation
98149
{
99150
request.TryAddHeader(
100151
headerGroup.Key,
101-
headerGroup.Select(x => x.Value).StringJoin(",")
152+
headerGroup.Select(x => x.Value).StringJoin(",").Trim(',', ' ')
102153
);
103154
}
104155

105-
return request.WrapTask();
106-
}
107-
108-
public async IAsyncEnumerable<HttpRequestMessageWrapper> ParseFileAsync(string filePath,
109-
[EnumeratorCancellation] CancellationToken cancellationToken = default)
110-
{
111-
var scripts = await File.ReadAllTextAsync(filePath, cancellationToken);
112-
var index = 0;
113-
foreach (var script in scripts.Split("\n###\n"))
114-
{
115-
var request = await ParseScriptAsync(script, cancellationToken);
116-
yield return new HttpRequestMessageWrapper($"request#{index}", request);
117-
index++;
118-
}
156+
return request;
119157
}
120158
}

0 commit comments

Comments
 (0)