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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions src/BuslyCLI.Console/Commands/CommonMessageSettings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.ComponentModel;
using BuslyCLI.TypeConverters;
using Spectre.Console;
using Spectre.Console.Cli;

Expand All @@ -25,15 +26,24 @@ public abstract class CommonMessageSettings : GlobalCommandSettings
public required string EnclosedMessageType { get; set; }

[CommandOption("-m|--message-body <message-body>")]
[Description("The content of the message body")]
public required string MessageBody { get; set; }
[Description("The content of the message body. Accepts a raw JSON string or a path to a file using curl-style @file syntax (e.g. @payload.json).")]
public required MessageBodyValue MessageBody { get; set; }

public override ValidationResult Validate()
{
if (string.IsNullOrWhiteSpace(ContentType)) return ValidationResult.Error("must specify a 'content-type'.");
if (string.IsNullOrWhiteSpace(EnclosedMessageType))
return ValidationResult.Error("must specify an 'enclosed-message-type'.");
if (string.IsNullOrWhiteSpace(MessageBody)) return ValidationResult.Error("must specify a 'message-body'.");

if (MessageBody is null) return ValidationResult.Error("must specify a 'message-body'.");
if (Path.IsPathFullyQualified(MessageBody.Value))
{
if (!File.Exists(MessageBody.Value))
return ValidationResult.Error($"File not found: {MessageBody.Value}");

MessageBody.Value = File.ReadAllText(MessageBody.Value);
}

return base.Validate();
}

Expand Down
2 changes: 1 addition & 1 deletion src/BuslyCLI.Console/Commands/NsbCommand/SendCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ protected override async Task<int> ExecuteAsync(CommandContext context, SendComm
var message = new OutgoingMessage(
Guid.NewGuid().ToString(),
headers,
Encoding.ASCII.GetBytes(settings.MessageBody)
Encoding.ASCII.GetBytes(settings.MessageBody.Value)
);

var transportOperation = new TransportOperation(
Expand Down
2 changes: 1 addition & 1 deletion src/BuslyCLI.Console/Commands/NsbEvent/PublishCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ protected override async Task<int> ExecuteAsync(CommandContext context, PublishC
var message = new OutgoingMessage(
Guid.NewGuid().ToString(),
headers,
Encoding.ASCII.GetBytes(settings.MessageBody)
Encoding.ASCII.GetBytes(settings.MessageBody.Value)
);

var type = CreateTypeFromString(settings.EnclosedMessageType);
Expand Down
2 changes: 1 addition & 1 deletion src/BuslyCLI.Console/Commands/NsbTimeout/SendTimeout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ protected override async Task<int> ExecuteAsync(CommandContext context, SendTime
var message = new OutgoingMessage(
Guid.NewGuid().ToString(),
headers,
Encoding.ASCII.GetBytes(settings.MessageBody)
Encoding.ASCII.GetBytes(settings.MessageBody.Value)
);

var dispatchProperties = new DispatchProperties();
Expand Down
27 changes: 27 additions & 0 deletions src/BuslyCLI.Console/TypeConverters/MessageBodyTypeConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.ComponentModel;

namespace BuslyCLI.TypeConverters;

public class MessageBodyTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}

public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{

if (value is string str)
{
// Curl-style @file reference: parse only, no I/O
if (str.StartsWith("@"))
{
var expandedFilePath = new ExpandedPath(str[1..]);
return new MessageBodyValue(expandedFilePath.Path);
}
}

return base.ConvertFrom(context, culture, value);
}
}
15 changes: 15 additions & 0 deletions src/BuslyCLI.Console/TypeConverters/MessageBodyValue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.ComponentModel;

namespace BuslyCLI.TypeConverters;

[TypeConverter(typeof(MessageBodyTypeConverter))]
public class MessageBodyValue
{
public string Value { get; set; }


public MessageBodyValue(string value)
{
Value = value;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using BuslyCLI.Commands.NsbCommand;
using BuslyCLI.TypeConverters;

namespace BuslyCLI.Console.Tests.Commands;

Expand All @@ -12,7 +13,7 @@ public void ShouldFailWhenDestinationEndpointIsMissing()
{
ContentType = "application/json",
EnclosedMessageType = "MessageContracts.Commands.CreateOrder",
MessageBody = "{}",
MessageBody = new MessageBodyValue("{}"),
DestinationEndpoint = null!
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using BuslyCLI.Commands.NsbEvent;
using BuslyCLI.TypeConverters;

namespace BuslyCLI.Console.Tests.Commands;

Expand All @@ -12,7 +13,7 @@ public void ShouldFailWhenContentTypeIsMissing()
{
ContentType = null!,
EnclosedMessageType = "MessageContracts.Commands.CreateOrder",
MessageBody = "{}"
MessageBody = new MessageBodyValue("{}")
};

var result = settings.Validate();
Expand All @@ -28,7 +29,7 @@ public void ShouldFailWhenEnclosedMessageTypeIsMissing()
{
ContentType = "application/json",
EnclosedMessageType = null!,
MessageBody = "{}"
MessageBody = new MessageBodyValue("{}")
};

var result = settings.Validate();
Expand All @@ -52,4 +53,21 @@ public void ShouldFailWhenMessageBodyIsMissing()
Assert.That(result.Successful, Is.False);
Assert.That(result.Message, Does.Contain("must specify a 'message-body'."));
}

[Test]
public void ShouldFailWhenMessageBodyFileDoesNotExist()
{
var nonExistentFilePath = Path.GetFullPath("non-existent-payload.json");
var settings = new PublishCommandSettings
{
ContentType = "application/json",
EnclosedMessageType = "MessageContracts.Commands.CreateOrder",
MessageBody = new MessageBodyValue(nonExistentFilePath)
};

var result = settings.Validate();

Assert.That(result.Successful, Is.False);
Assert.That(result.Message, Does.Contain($"File not found: {nonExistentFilePath}"));
}
}
3 changes: 1 addition & 2 deletions website/docs/cli-reference/command/send.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ busly send command
| ------------------------------- | ----------------------------------------------------------------------------------------------- |
| `-c`, `--content-type` | The fully qualified .NET type name of the enclosed message (ex: Ordering.Commands.CreateOrder ) |
| `-e`, `--enclosed-message-type` | The type of serialization used for the message |
| `-m`, `--message-body` | The content of the message body |
| `-d`, `--destination-endpoint` | The destination endpoint to send a message to |
| `-m`, `--message-body` | The content of the message body. Accepts a raw JSON string or a path to a file using curl-style `@` syntax (e.g. `@payload.json`). |

## Examples

Expand Down
2 changes: 1 addition & 1 deletion website/docs/cli-reference/event/publish.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ busly event publish
| ------------------------------- | ----------------------------------------------------------------------------------------------- |
| `-c`, `--content-type` | The fully qualified .NET type name of the enclosed message (ex: Ordering.Commands.CreateOrder ) |
| `-e`, `--enclosed-message-type` | The type of serialization used for the message |
| `-m`, `--message-body` | The content of the message body |
| `-m`, `--message-body` | The content of the message body. Accepts a raw JSON string or a path to a file using curl-style `@` syntax (e.g. `@payload.json`). |

## Examples

Expand Down
2 changes: 1 addition & 1 deletion website/docs/cli-reference/timeout/send.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ busly timeout send
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `-c`, `--content-type` | The fully qualified .NET type name of the enclosed message (ex: Ordering.Commands.CreateOrder ) |
| `-e`, `--enclosed-message-type` | The type of serialization used for the message |
| `-m`, `--message-body` | The content of the message body |
| `-m`, `--message-body` | The content of the message body. Accepts a raw JSON string or a path to a file using curl-style `@` syntax (e.g. `@payload.json`). |
| `-d`, `--destination-endpoint` | The destination endpoint to send a message to |
| `--do-not-deliver-before` | Allows specifying a date before which the delivery should not occur, using ISO-8601 format (YYYY-MM-DDTHH:mm:ssZ) |
| `--delay-delivery-with` | Specifies the delay before the timeout is delivered, using a TimeSpan format |
Expand Down
79 changes: 78 additions & 1 deletion website/docs/introduction/quick-start.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -290,4 +290,81 @@ docker run --rm `
```

</TabItem>
</Tabs>
</Tabs>

## Passing Message Body from a File

As you can see in the commands above, inlining JSON directly in the terminal can get complicated fast — especially in PowerShell, where double quotes require manual escaping. For anything beyond the simplest payloads, this becomes error-prone and hard to read.

Busly supports a curl-style `@filename` syntax for `--message-body`, letting you store your JSON in a file and reference it directly. Where `payload.json` contains:

```json
{
"OrderNumber": "3f2d6c8a-b7a2-4c3f-9c3e-12ab45ef6789"
}
```

<Tabs>
<TabItem value="bash" label="Bash">

```bash
busly command send \
--content-type 'text/json' \
--enclosed-message-type "Messages.Commands.CreateOrder" \
--destination-endpoint "BuslyCLI.DemoEndpoint" \
--message-body @payload.json
```

</TabItem>
<TabItem value="powershell" label="PowerShell">

```bash
busly command send `
--content-type 'text/json' `
--enclosed-message-type "Messages.Commands.CreateOrder" `
--destination-endpoint "BuslyCLI.DemoEndpoint" `
--message-body @payload.json
```

</TabItem>
<TabItem value="docker bash" label="Docker (Bash)">

When running via Docker, mount the payload file into the container using `-v` and reference its container path with `@`:

```bash
docker run --rm \
--network host \
-v "$HOME/.busly-cli/config.yaml:/app/config.yaml" \
-v "$(pwd)/payload.json:/app/payload.json" \
tragiccode/busly-cli \
command send \
--content-type "text/json" \
--enclosed-message-type "Messages.Commands.CreateOrder" \
--destination-endpoint "BuslyCLI.DemoEndpoint" \
--message-body @/app/payload.json \
--config ./config.yaml
```

</TabItem>
<TabItem value="docker powershell" label="Docker (PowerShell)">

When running via Docker, mount the payload file into the container using `-v` and reference its container path with `@`:

```bash
docker run --rm `
--network host `
-v "$HOME/.busly-cli/config.yaml:/app/config.yaml" `
-v "${PWD}/payload.json:/app/payload.json" `
tragiccode/busly-cli `
command send `
--content-type "text/json" `
--enclosed-message-type "Messages.Commands.CreateOrder" `
--destination-endpoint "BuslyCLI.DemoEndpoint" `
--message-body @/app/payload.json `
--config ./config.yaml
```

</TabItem>
</Tabs>

No escaping required on any platform. Relative paths are resolved from your current working directory, and `~` is expanded to your home directory.
Loading