feat(cli): --element-type and JSON --value for List attributes
This commit is contained in:
@@ -138,9 +138,10 @@ public static class TemplateCommands
|
||||
var templateIdOption = new Option<int>("--template-id") { Description = "Template ID", Required = true };
|
||||
var nameOption = new Option<string>("--name") { Description = "Attribute name", Required = true };
|
||||
var dataTypeOption = new Option<string>("--data-type") { Description = "Data type", Required = true };
|
||||
var valueOption = new Option<string?>("--value") { Description = "Default value" };
|
||||
var valueOption = new Option<string?>("--value") { Description = "Default value. For a List attribute, supply a JSON array (e.g. '[\"WO-1\",\"WO-2\"]')." };
|
||||
var descOption = new Option<string?>("--description") { Description = "Description" };
|
||||
var sourceOption = new Option<string?>("--data-source") { Description = "Data source reference" };
|
||||
var elementTypeOption = new Option<string?>("--element-type") { Description = ElementTypeOptionDescription };
|
||||
var lockedOption = new Option<bool>("--locked") { Description = "Lock status" };
|
||||
lockedOption.DefaultValueFactory = _ => false;
|
||||
|
||||
@@ -151,28 +152,39 @@ public static class TemplateCommands
|
||||
addCmd.Add(valueOption);
|
||||
addCmd.Add(descOption);
|
||||
addCmd.Add(sourceOption);
|
||||
addCmd.Add(elementTypeOption);
|
||||
addCmd.Add(lockedOption);
|
||||
addCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var dataType = result.GetValue(dataTypeOption)!;
|
||||
var elementType = result.GetValue(elementTypeOption);
|
||||
if (!TryValidateElementType(dataType, elementType, out var error))
|
||||
{
|
||||
OutputFormatter.WriteError(error!, "INVALID_ARGUMENT");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||
new AddTemplateAttributeCommand(
|
||||
BuildAddAttributeCommand(
|
||||
result.GetValue(templateIdOption),
|
||||
result.GetValue(nameOption)!,
|
||||
result.GetValue(dataTypeOption)!,
|
||||
dataType,
|
||||
result.GetValue(valueOption),
|
||||
result.GetValue(descOption),
|
||||
result.GetValue(sourceOption),
|
||||
result.GetValue(lockedOption)));
|
||||
result.GetValue(lockedOption),
|
||||
elementType));
|
||||
});
|
||||
group.Add(addCmd);
|
||||
|
||||
var updateIdOption = new Option<int>("--id") { Description = "Attribute ID", Required = true };
|
||||
var updateNameOption = new Option<string>("--name") { Description = "Attribute name", Required = true };
|
||||
var updateDataTypeOption = new Option<string>("--data-type") { Description = "Data type", Required = true };
|
||||
var updateValueOption = new Option<string?>("--value") { Description = "Default value" };
|
||||
var updateValueOption = new Option<string?>("--value") { Description = "Default value. For a List attribute, supply a JSON array (e.g. '[\"WO-1\",\"WO-2\"]')." };
|
||||
var updateDescOption = new Option<string?>("--description") { Description = "Description" };
|
||||
var updateSourceOption = new Option<string?>("--data-source") { Description = "Data source reference" };
|
||||
var updateElementTypeOption = new Option<string?>("--element-type") { Description = ElementTypeOptionDescription };
|
||||
var updateLockedOption = new Option<bool>("--locked") { Description = "Lock status" };
|
||||
updateLockedOption.DefaultValueFactory = _ => false;
|
||||
|
||||
@@ -183,19 +195,29 @@ public static class TemplateCommands
|
||||
updateCmd.Add(updateValueOption);
|
||||
updateCmd.Add(updateDescOption);
|
||||
updateCmd.Add(updateSourceOption);
|
||||
updateCmd.Add(updateElementTypeOption);
|
||||
updateCmd.Add(updateLockedOption);
|
||||
updateCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var dataType = result.GetValue(updateDataTypeOption)!;
|
||||
var elementType = result.GetValue(updateElementTypeOption);
|
||||
if (!TryValidateElementType(dataType, elementType, out var error))
|
||||
{
|
||||
OutputFormatter.WriteError(error!, "INVALID_ARGUMENT");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||
new UpdateTemplateAttributeCommand(
|
||||
BuildUpdateAttributeCommand(
|
||||
result.GetValue(updateIdOption),
|
||||
result.GetValue(updateNameOption)!,
|
||||
result.GetValue(updateDataTypeOption)!,
|
||||
dataType,
|
||||
result.GetValue(updateValueOption),
|
||||
result.GetValue(updateDescOption),
|
||||
result.GetValue(updateSourceOption),
|
||||
result.GetValue(updateLockedOption)));
|
||||
result.GetValue(updateLockedOption),
|
||||
elementType));
|
||||
});
|
||||
group.Add(updateCmd);
|
||||
|
||||
@@ -213,6 +235,82 @@ public static class TemplateCommands
|
||||
return group;
|
||||
}
|
||||
|
||||
/// <summary>Shared description for the <c>--element-type</c> option on attribute add/update.</summary>
|
||||
internal const string ElementTypeOptionDescription =
|
||||
"Element scalar type for a List attribute (String, Int32, Float, Double, Boolean, DateTime). Required when --data-type is List.";
|
||||
|
||||
/// <summary>The element scalar types permitted for a List attribute (matches the Management API).</summary>
|
||||
private static readonly string[] ValidElementScalars =
|
||||
{ "String", "Int32", "Float", "Double", "Boolean", "DateTime" };
|
||||
|
||||
/// <summary>
|
||||
/// Validates the <c>--data-type</c> / <c>--element-type</c> combination client-side so
|
||||
/// the CLI fails fast with a clear message before contacting the Management API (the
|
||||
/// server validates independently). A List attribute requires a valid element scalar;
|
||||
/// a non-List attribute must not carry an element type. Comparison is case-insensitive.
|
||||
/// </summary>
|
||||
/// <param name="dataType">The raw <c>--data-type</c> value.</param>
|
||||
/// <param name="elementType">The raw <c>--element-type</c> value, or null if absent.</param>
|
||||
/// <param name="error">A descriptive error message when validation fails; otherwise null.</param>
|
||||
/// <returns><c>true</c> when the combination is valid; otherwise <c>false</c>.</returns>
|
||||
internal static bool TryValidateElementType(string dataType, string? elementType, out string? error)
|
||||
{
|
||||
error = null;
|
||||
var isList = string.Equals(dataType, "List", StringComparison.OrdinalIgnoreCase);
|
||||
var hasElementType = !string.IsNullOrWhiteSpace(elementType);
|
||||
|
||||
if (isList)
|
||||
{
|
||||
if (!hasElementType)
|
||||
{
|
||||
error = "--element-type is required when --data-type is List "
|
||||
+ "(one of: String, Int32, Float, Double, Boolean, DateTime).";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ValidElementScalars.Contains(elementType!.Trim(), StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
error = $"Invalid --element-type '{elementType}'. Valid List element scalars are: "
|
||||
+ string.Join(", ", ValidElementScalars) + ".";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasElementType)
|
||||
{
|
||||
error = "--element-type is only valid when --data-type is List.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the <see cref="AddTemplateAttributeCommand"/> payload sent to the Management API.
|
||||
/// The raw <paramref name="value"/> string is forwarded unchanged — for a List attribute it
|
||||
/// is a JSON array, which the API/codec parses; the CLI does not reshape it.
|
||||
/// </summary>
|
||||
internal static AddTemplateAttributeCommand BuildAddAttributeCommand(
|
||||
int templateId, string name, string dataType, string? value,
|
||||
string? description, string? dataSource, bool isLocked, string? elementType)
|
||||
=> new(templateId, name, dataType, value, description, dataSource, isLocked, NormalizeElementType(elementType));
|
||||
|
||||
/// <summary>
|
||||
/// Builds the <see cref="UpdateTemplateAttributeCommand"/> payload sent to the Management API.
|
||||
/// The raw <paramref name="value"/> string is forwarded unchanged — for a List attribute it
|
||||
/// is a JSON array, which the API/codec parses; the CLI does not reshape it.
|
||||
/// </summary>
|
||||
internal static UpdateTemplateAttributeCommand BuildUpdateAttributeCommand(
|
||||
int attributeId, string name, string dataType, string? value,
|
||||
string? description, string? dataSource, bool isLocked, string? elementType)
|
||||
=> new(attributeId, name, dataType, value, description, dataSource, isLocked, NormalizeElementType(elementType));
|
||||
|
||||
/// <summary>Trims a non-empty element type; an empty/whitespace value becomes null (no element type).</summary>
|
||||
private static string? NormalizeElementType(string? elementType)
|
||||
=> string.IsNullOrWhiteSpace(elementType) ? null : elementType.Trim();
|
||||
|
||||
private static Command BuildAlarm(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var group = new Command("alarm") { Description = "Manage template alarms" };
|
||||
|
||||
@@ -164,32 +164,50 @@ scadabridge --url <url> template validate --id <int>
|
||||
Add an attribute to a template.
|
||||
|
||||
```sh
|
||||
scadabridge --url <url> template attribute add --template-id <int> --name <string> --data-type <string> [--default-value <string>] [--tag-path <string>]
|
||||
scadabridge --url <url> template attribute add --template-id <int> --name <string> --data-type <string> [--value <string>] [--element-type <string>] [--description <string>] [--data-source <string>] [--locked]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--template-id` | yes | Template ID |
|
||||
| `--name` | yes | Attribute name |
|
||||
| `--data-type` | yes | Attribute data type (e.g. `Float`, `Int`, `String`, `Bool`) |
|
||||
| `--default-value` | no | Default value |
|
||||
| `--tag-path` | no | Data connection tag path |
|
||||
| `--data-type` | yes | Attribute data type (`Boolean`, `Int32`, `Float`, `Double`, `String`, `DateTime`, `List`) |
|
||||
| `--value` | no | Default value. For a `List` attribute, supply a JSON array (e.g. `'["WO-1","WO-2"]'`); the raw string is forwarded to the API, which parses it |
|
||||
| `--element-type` | no | Element scalar type for a `List` attribute (`String`, `Int32`, `Float`, `Double`, `Boolean`, `DateTime`). **Required when `--data-type` is `List`**; must be omitted otherwise |
|
||||
| `--description` | no | Description |
|
||||
| `--data-source` | no | Data source reference |
|
||||
| `--locked` | no | Lock the attribute in derived templates |
|
||||
|
||||
**List example** — add a multi-value String attribute with two default elements:
|
||||
|
||||
```sh
|
||||
scadabridge --url <url> template attribute add --template-id 7 --name WorkOrders \
|
||||
--data-type List --element-type String --value '["WO-1","WO-2"]'
|
||||
```
|
||||
|
||||
The CLI validates the data-type / element-type combination locally before calling the
|
||||
API: `--data-type List` requires a valid `--element-type`, and `--element-type` may only
|
||||
be supplied when `--data-type` is `List`. The Management API re-validates server-side.
|
||||
|
||||
#### `template attribute update`
|
||||
|
||||
Update an attribute on a template.
|
||||
Update an attribute on a template. An update **replaces** the whole entity — every
|
||||
required field below must be supplied with its post-update value, even if unchanged.
|
||||
|
||||
```sh
|
||||
scadabridge --url <url> template attribute update --template-id <int> --name <string> [--data-type <string>] [--default-value <string>] [--tag-path <string>]
|
||||
scadabridge --url <url> template attribute update --id <int> --name <string> --data-type <string> [--value <string>] [--element-type <string>] [--description <string>] [--data-source <string>] [--locked]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--template-id` | yes | Template ID |
|
||||
| `--name` | yes | Attribute name to update |
|
||||
| `--data-type` | no | Updated data type |
|
||||
| `--default-value` | no | Updated default value |
|
||||
| `--tag-path` | no | Updated tag path |
|
||||
| `--id` | yes | Attribute ID |
|
||||
| `--name` | yes | Attribute name |
|
||||
| `--data-type` | yes | Attribute data type (`Boolean`, `Int32`, `Float`, `Double`, `String`, `DateTime`, `List`) |
|
||||
| `--value` | no | Default value. For a `List` attribute, supply a JSON array (e.g. `'["WO-1","WO-2"]'`) |
|
||||
| `--element-type` | no | Element scalar type for a `List` attribute (`String`, `Int32`, `Float`, `Double`, `Boolean`, `DateTime`). **Required when `--data-type` is `List`**; must be omitted otherwise |
|
||||
| `--description` | no | Description |
|
||||
| `--data-source` | no | Data source reference |
|
||||
| `--locked` | no | Lock the attribute in derived templates |
|
||||
|
||||
#### `template attribute delete`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user