Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework input validation #222

Merged
merged 46 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
f66b277
Create CommandInputType
MASSHUU12 Feb 18, 2024
5ed9813
Add IsArray property to CommandInputType
MASSHUU12 Feb 18, 2024
4d99900
Update CommandInputType to use float for Min and Max values
MASSHUU12 Feb 18, 2024
a865c6b
Add TryParseCommandInputType method to Text.cs
MASSHUU12 Feb 18, 2024
eda8b8a
Create CommandInput attribute
MASSHUU12 Feb 18, 2024
f0c6509
Argument and Option attributes inherit from CommandInput
MASSHUU12 Feb 18, 2024
692b0a4
Add support for multiple command input types
MASSHUU12 Feb 18, 2024
0f2b92d
Overloaded IsWithinRange method to support multiple types
MASSHUU12 Feb 19, 2024
e7870ee
Add InvalidOption method to Messages class
MASSHUU12 Feb 19, 2024
2323e24
Rewrite CommandValidator
MASSHUU12 Feb 19, 2024
a31ddfa
Adjust manual generation
MASSHUU12 Feb 19, 2024
91be9dd
Adjust tests
MASSHUU12 Feb 19, 2024
25940f1
Create LinkedList object
MASSHUU12 Feb 19, 2024
29ca1b3
Adjust CommandInfo
MASSHUU12 Feb 19, 2024
902aef5
Fix generating manual
MASSHUU12 Feb 19, 2024
dc86870
Add more tests for TryParseCommandInputType method
MASSHUU12 Feb 19, 2024
53a675c
Update csproj
MASSHUU12 Feb 19, 2024
8dbc934
Update csproj
MASSHUU12 Feb 20, 2024
c1f43d7
Options with null type are no longer supported
MASSHUU12 Feb 20, 2024
4fc67db
Add null check for type
MASSHUU12 Feb 20, 2024
fcb6b25
Fix null reference exception
MASSHUU12 Feb 20, 2024
45e1b89
Improve debug launch configurations (breakpoints now work)
MASSHUU12 Feb 20, 2024
549998a
Correctly handle boolean options
MASSHUU12 Feb 20, 2024
ae59b52
Add ValueOutOfRange & ArgumentValueOutOfRange messages
MASSHUU12 Feb 20, 2024
6ddcc55
Better handle errors & value range
MASSHUU12 Feb 20, 2024
d4609fe
Remove unnecessary comments
MASSHUU12 Feb 20, 2024
75352b8
Use StringName where it makes sense
MASSHUU12 Feb 20, 2024
657b9cc
Adjust code to use StringName
MASSHUU12 Feb 20, 2024
9ed88f1
Use StringName's
MASSHUU12 Feb 20, 2024
595ca08
Update InvalidArgument message
MASSHUU12 Feb 20, 2024
3ca06eb
Add support for limiting string length
MASSHUU12 Feb 20, 2024
e269b79
Add EStringConversionResult
MASSHUU12 Feb 20, 2024
19fa7bf
Methods StartsWith & EndsWith from Text class are now extensions of s…
MASSHUU12 Feb 20, 2024
22f5bb1
It is no longer possible to call the history command via itself
MASSHUU12 Feb 21, 2024
c6e2398
Add tag to the project
MASSHUU12 Feb 21, 2024
7ee1e43
Fix issues with casting & error logging
MASSHUU12 Feb 21, 2024
1e48a33
Fix: The set command throws an exception when there are no registered…
MASSHUU12 Feb 21, 2024
1bc3643
Adjust commands to the new system
MASSHUU12 Feb 21, 2024
95df641
Make it possible to convert to int type
MASSHUU12 Feb 21, 2024
29ae549
Options with bool type no longer need to specify default value
MASSHUU12 Feb 21, 2024
2826c48
Adjust commands
MASSHUU12 Feb 21, 2024
68409c6
Change how ranges with one value are handled
MASSHUU12 Feb 21, 2024
317a52a
Improve converting passed value to a list
MASSHUU12 Feb 21, 2024
e88c761
Simplify echo command
MASSHUU12 Feb 21, 2024
ef76e1a
Update AUTOMATIC_INPUT_VALIDATION.md
MASSHUU12 Feb 21, 2024
389e866
Update script templates & changelog
MASSHUU12 Feb 21, 2024
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
26 changes: 25 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
"program": "${env:GODOT}",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false
"stopAtEntry": false,
"justMyCode": false,
"suppressJITOptimizations": true
},
// Debug the scene that matches the name of the currently open *.cs file
// (if there's a scene with the same name in the same directory).
Expand All @@ -26,6 +28,28 @@
"program": "${env:GODOT}",
"args": ["${fileDirname}/${fileBasenameNoExtension}.tscn"],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"justMyCode": false,
"suppressJITOptimizations": true
},
{
"name": "🕹 Run Game",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${env:GODOT}",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false
},
{
"name": "🎭 Run Current Scene",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${env:GODOT}",
"args": ["${fileDirname}/${fileBasenameNoExtension}.tscn"],
"cwd": "${workspaceFolder}",
"stopAtEntry": false
}
]
Expand Down
24 changes: 23 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,50 @@ All notable changes to this project will be documented in this file.
- More results to ECommandResult.
- CommandResult record.
- Static methods to ICommand that corresponds to the ECommandResult results.
- ESceneChangeFailureReason enum.
- Networking helper class.
- NetworkingOptions record.
- -limit option to ping command.
- CommandInputType record.
- TryParseCommandInputType method for Text helper class.
- String type in arguments and options can have defined min and max length.
- Enums:
- ESceneChangeFailureReason
- EStringConversionResult
- Commands:
- TraceRoute
- Load
- Attributes:
- Description
- Usage
- CommandInput
- Messages:
- InvalidOption,
- ValueOutOfRange

### Changed

- Renamed CommandResult to ECommandResult.
- Commands now return CommandResult record.
- The SceneAboutToChange signal in the Cs command now also gives the old path, not just the new one.
- Renamed ping command options.
- Argument & Option attributes inherit from CommandInput attribute.
- Options with null type are no longer supported.
- The way of defining the type for arguments and options has changed.
- CommandData now uses StringName instead of string for dictionaries.
- Methods StartsWith & EndsWith from Text class are now extensions of string type.
- Using default value for bool type options is no longer necessary.
- Double is no longer a valid type for options & arguments.
- Updated AUTOMATIC_INPUT_VALIDATION.md & script templates.

### Removed

- FailureReason enum.

### Fixed

- It is possible to call the history command via itself, which can cause an endless loop.
- The set command throws an exception when there are no registered extensions.

## [1.23.0-beta 2024-02-13]

### Added
Expand Down
117 changes: 77 additions & 40 deletions addons/yat/docs/AUTOMATIC_INPUT_VALIDATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,79 +3,100 @@
<p>Here you will find information on how YAT can perform validation of arguments, and options for your commands automatically.</p>
</div>

## Automatic input Validation
<br />

> If you want documentation to be created based on attributes,
> but do not want validation you can use the `NoValidate` attribute.

Custom input validation gives you more options, but can be cumbersome and unnecessarily lengthy commands. For this reason, YAT supports two ways of validating data: required (arguments), and optional (options).
Custom input validation is more flexible, but at the same time cumbersome, it can negatively affect the readability of the code as well as the time it takes to create a command. For this reason, YAT allows automatic input validation in two ways: required arguments and optional options.

After the validation is complete, YAT returns the data converted to the appropriate type, or cancels the command execution if a validation error occurs.
During validation, YAT checks whether the submitted data meets the relevant requirements and additionally performs conversion to the appropriate type. If the data does not meet the requirements, YAT displays an appropriate message.

For information on how to get data that has passed validation, see [CREATING_COMMANDS.md](./CREATING_COMMANDS.md).
> For information on how to get data that has passed validation, see [CREATING_COMMANDS.md](./CREATING_COMMANDS.md).

### Defining data type requirements

Both arguments and options allow flexibility in creating requirements for the type of data to be submitted.

The created requirement can consist of a single type, e.g., `int`, `float`. It can also take several options like `int|float`, or `yes|no`. Multiple options are separated by a pipe `|`.

Supported data types:

- string
- int
- float
- double
- bool
- constant string (e.q., `yes`, `normal`, `wireframe` etc.)

### Arguments
Both `string`, `int` and `float` support defining the ranges they can take. In the case of `string` it is the minimum and maximum length of the string, in the case of `int` and `float` it is the minimum and maximum value.

> The order of arguments above the class matters.
The limit for ranges is `-3.4028235E+38` and `3.4028235E+38`.

Arguments are required, if an argument is missing or data that does not meet the requirements is passed,
validation will fail and the command will not run.
Example of a requirement with one type and a range of values:

```c
int(5:15) -> The value passed can only be an integer in the range 5 -15.
```

An example of a requirement with many possibilities:

You can specify what arguments, under what rules and in what order the command expects using the `Argument` attribute.
```
normal|unshaded|wireframe|int(0:30)
```

In this case, the transferred data can take either a numeric value from 0 to 30 or one of the specified strings.

Arguments are defined as follows:
#### Creating a range

- `argument` - the user must use the name of this argument.
- `argument:data_type` - the user must use the given data type in place of this argument.
- `argument:[option1, option2]` - the user must use any of the given options in place of the argument.
- `argument:[option1, data_type, option3]` - the user must specify any of the given options or use the listed data type in place of the given argument
The range is created in parentheses given after the type that supports the range. The minimum and maximum values must be separated by a colon `:`.

Numeric types can accept ranges of values. For example:
One of the values may be omitted (the colon must still be present), in which case:
- the minimum value is omitted: then it takes the value `-3.4028235E+38`,
- maximum value is omitted: then it takes the value `3.4028235E+38`.

```csharp
[Argument("step", "double(0, 69.420)", "Number of steps.")]
Example of a range of values with both limits given:

```cs
int(5:15) -> range is 5 - 15
```

Example of use:
Example of a range of values with only maximum limit given:

```csharp
[Argument("action", "[move, jump]", "Action to perform.")]
[Argument("direction", "[left, right, int]", "Direction to move/jump.")]
```cs
int(:15) -> range is -3.4028235E+38 - 15
```

In the above example, the command takes two arguments.
The first argument can take one of two values: `move` or `jump`.
The second argument has three possibilities, it can take `"left"`, `"right"` or a `number`.
Example of a range of values with only minimum limit given:

### Options
```cs
int(5:) -> range is 5 - 3.4028235E+38
```

> Options are not required, however, if an option is passed,
> but the data does not match, validation will fail and the command will not run.
### Arguments

Options are defined as follows:
> The order of arguments matters.

- `-opt` - passed option must match the name of the option. If option is present it returns `true` if not `null`.
- `-opt=data_type` - value of passed option must be of the specified data type.
- `-opt=option1|data_type|option3` - value of passed option must be of the specified data type or one of the specified options.
- `-opt=data_type...` - value of passed option must be an array of values of the specified data type.
Arguments are required, if an argument is missing or data that does not meet the requirements is passed,
validation will fail and the command will not run.

Numeric types can accept ranges of values. For example:
You can specify arguments using the `Argument` attribute:

```csharp
[Option("-step", "double(0:69.420)", "Number of steps to move.")]
```cs
[Argument("action", "move|jump", "Action to perform.")]
[Argument("direction", "left|right|int", "Direction to move/jump.")]
```

Example of use:
In the above example, the command takes two arguments.
The first argument can take one of two values: `move` or `jump`.
The second argument has three possibilities, it can take `"left"`, `"right"` or a `number`.

### Options

Options are **not** required, however, if an option is passed, but the data does not match requirements, validation will fail and the command will not run.

```csharp
You can specify options using the `Option` attribute:

```cs
[Option("-action", "move|jump", "Action to perform.")]
[Option("-direction", "left|right|int(-1:1)", "Direction to move.")]
```
Expand All @@ -84,11 +105,27 @@ In the above example, command can take two options.
The first option can take one of two values: `move` or `jump`.
The second option has three possibilities, it can take `"left"`, `"right"` or a `number` limited to a range from -1 to 1.

#### Arrays

Options, unlike arguments, can also take an array of data of a given type, you just need to add `...` at the end of the type definition.

Example:

```cs
[Option("-p", "int(1:99)...", "Lorem ipsum.", new int[] { 1 })]
```

In this case, the transfer of data to the option is as follows:

```sh
some_command -p=5,2,32,47
```

#### Default values

Options also support `default values`, which will be assigned to them when the user does not pass an option when running the command. If the default value is not set, and the user does not use the option, then its value is set to `null`:
Options also support `default values`, which will be assigned to them when the user does not pass an option when running the command. If the default value is not set, and the user does not use the option, then its value is set to `null` (`false` if type is bool):

```csharp
```cs
[Option("-action", "move|jump", "Action to perform.", "move")]
```

Expand Down
33 changes: 3 additions & 30 deletions addons/yat/src/attributes/ArgumentAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,39 +1,12 @@
using System;
using System.Linq;

namespace YAT.Attributes;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class ArgumentAttribute : Attribute
public sealed class ArgumentAttribute : CommandInputAttribute
{
public string Name { get; private set; }
public object Type { get; set; }
public string Description { get; private set; }

public ArgumentAttribute(string name, string type, string Description = "")
{
Name = name;
Type = ParseDataType(type);
this.Description = Description;
}

/// <summary>
/// Parses a string representation of a data type and returns the corresponding object or type.
/// </summary>
/// <param name="dataType">The string representation of the data type to parse.</param>
/// <returns>The parsed object or type, or null if the data type could not be parsed.</returns>
private static object ParseDataType(string dataType)
public ArgumentAttribute(string name, string type, string description = "")
: base(name, type, description)
{
var data = dataType?.Trim();

if (string.IsNullOrEmpty(data)) return null;

if (data.StartsWith("[") && data.EndsWith("]"))
{
string[] values = data.Trim('[', ']').Split(',').Select(v => v.Trim()).ToArray();
return values;
}

return data;
}
}
32 changes: 32 additions & 0 deletions addons/yat/src/attributes/CommandInputAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using Godot;
using YAT.Helpers;
using YAT.Types;

namespace YAT.Attributes;

[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public class CommandInputAttribute : Attribute
{
public StringName Name { get; private set; }
public LinkedList<CommandInputType> Types { get; private set; } = new();
public string Description { get; private set; }

public CommandInputAttribute(StringName name, string type, string description = "")
{
Name = name;
Description = description;

if (string.IsNullOrEmpty(type))
GD.PushError($"Invalid command input type '{type}' for command '{name}'.");

foreach (var t in type.Split('|'))
{
if (Text.TryParseCommandInputType(t, out var commandInputType))
Types.AddLast(commandInputType);
else
GD.PushError($"Invalid command input type '{t}' for command '{name}'.");
}
}
}
32 changes: 3 additions & 29 deletions addons/yat/src/attributes/OptionAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,13 @@
namespace YAT.Attributes;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class OptionAttribute : Attribute
public sealed class OptionAttribute : CommandInputAttribute
{
public string Name { get; private set; }
public string Description { get; private set; }
public object Type { get; set; }
public object DefaultValue { get; private set; }

public OptionAttribute(
string name,
string type,
string description = "",
object defaultValue = null
)
public OptionAttribute(string name, string type, string description = "", object defaultValue = null)
: base(name, type, description)
{
Name = name;
Type = ParseDataType(type);
Description = description;
DefaultValue = defaultValue;
}

/// <summary>
/// Parses the given data type string and returns an object representing the parsed data.
/// </summary>
/// <param name="dataType">The data type string to parse.</param>
/// <returns>An object representing the parsed data.</returns>
private static object ParseDataType(string dataType)
{
var data = dataType?.Trim();

if (string.IsNullOrEmpty(data)) return null;

var splitted = data.Split(',');

return splitted.Length > 1 ? splitted : splitted[0];
}
}
Loading