Skip to content

Commit

Permalink
lestarch: command raw text input and fixed client validation
Browse files Browse the repository at this point in the history
  • Loading branch information
LeStarch committed Jul 26, 2021
1 parent 0392617 commit cb8aede
Show file tree
Hide file tree
Showing 11 changed files with 336 additions and 64 deletions.
6 changes: 6 additions & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ bytearray
calibri
callergraph
callgraph
cargs
cata
cbl
CCB
Expand Down Expand Up @@ -200,6 +201,7 @@ figcaption
filemode
fileno
filepath
fileurl
finditer
Firefox
flac
Expand Down Expand Up @@ -293,6 +295,7 @@ janamian
javadoc
javascript
Jax
Jdk
joinpath
jpe
jpeg
Expand Down Expand Up @@ -376,6 +379,7 @@ noapp
noqa
nosort
Noto
novalidate
nowait
nowrap
NSPACES
Expand Down Expand Up @@ -556,6 +560,7 @@ telem
testcase
TESTLIST
textarea
textbox
tgz
thead
thtcp
Expand Down Expand Up @@ -624,6 +629,7 @@ utils
vals
valuemin
valuenow
Vcs
versionchanged
versioning
vexc
Expand Down
6 changes: 5 additions & 1 deletion src/fprime_gds/common/data_types/cmd_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ def __init__(self, cmd_args, cmd_temp, cmd_time=None):
self.convert_arg_value(val, typ)
errors.append("")
except Exception as exc:
errors.append(str(exc))
error_message = str(exc)
# Patch old versions of fprime-tools to replace a bad error messgae with the correct one
if isinstance(exc, TypeError) and "object of type 'NoneType' has no len()" in error_message:
error_message = f"String size {len(val)} is greater than {typ.__max_string_len}!"
errors.append(error_message)
# If any errors occur, then raise a aggregated error
if [error for error in errors if error != ""]:
raise CommandArgumentsException(errors)
Expand Down
8 changes: 4 additions & 4 deletions src/fprime_gds/flask/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ def put(self, command):
self.sender.send_command(command, arg_list)
except fprime.common.models.serialize.type_exceptions.NotInitializedException:
flask_restful.abort(403, message="Did not supply all required arguments.")
except fprime_gds.common.data_types.cmd_data.CommandArgumentException as exc:
flask_restful.abort(403, message=str(exc))
# except fprime_gds.common.data_types.cmd_data.CommandArgumentsException as exc:
# flask_restful.abort(403, message="Argument errors occurred", errors=exc.errors)
except fprime_gds.common.data_types.cmd_data.CommandArgumentsException as exc:
flask_restful.abort(403, message={"errors": exc.errors})
except KeyError as key_error:
flask_restful.abort(403, message="{} is not a valid command".format(key_error))
return {"message": "success"}
2 changes: 2 additions & 0 deletions src/fprime_gds/flask/static/.idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/fprime_gds/flask/static/.idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions src/fprime_gds/flask/static/.idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions src/fprime_gds/flask/static/.idea/static.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/fprime_gds/flask/static/.idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 43 additions & 23 deletions src/fprime_gds/flask/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -177,25 +177,45 @@
<div class="fp-flex-repeater">
<div class="fp-flex-header">
<h2>Sending Command: {{ selected.full_name }}</h2>
<div class="form-row">
<div class="form-group col-4">
<label for="mnemonic">Mnemonic</label>
<v-select id="mnemonic" style="flex: 1 1 auto;"
:clearable="false" :searchable="true"
:filterable="true" label="full_name" :options="commandList" v-model="selected">
</v-select>
<form v-on:submit.prevent="null" class="command-input-form" novalidate>
<div class="form-row">
<div class="form-group col-4">
<label for="mnemonic">Mnemonic</label>
<v-select id="mnemonic" style="flex: 1 1 auto;"
:clearable="false" :searchable="true" @input="validate"
:filterable="true" label="full_name" :options="commandList" v-model="selected"
:class="this.error == '' ? '' : 'is-invalid'" required>
</v-select>
<div class="invalid-feedback">{{ (this.error != '')? this.error : "Supply valid command"}}</div>
</div>
</div>
</div>
<div class="form-row">
<div class="form-group col" v-for="argument in selected.args">
<command-argument :argument="argument"></command-argument>
<div class="form-row">
<div class="form-group col" v-for="argument in selected.args">
<command-argument :argument="argument"></command-argument>
</div>
</div>
</div>
<div class="row no-gutters">
<button type="button" class="col-2 btn btn-outline-secondary" v-on:click="clearArguments">Clear Arguments</button>
<button type="button" :disabled="active" class="col-2 btn btn-primary" v-on:click="sendCommand">
Send Command
</button>
<div class="form-row no-gutters">
<button type="button" class="col-2 btn btn-outline-secondary" v-on:click="clearArguments">Clear Arguments</button>
<button type="button" v-on:click="sendCommand" :disabled="active" class="col-2 btn btn-primary">
Send Command
</button>
</div>
</form>
</div>
<command-text :selected="selected"></command-text>
</div>
</template>
<!-- Command Text:
Input textbox for entering in a command
-->
<template id="command-text-template">
<div class="fp-flex-repeater">
<div class="form-row">
<div class="form-group col-12">
<h5>Command String</h5>
<input type="text" name="command-text" id="command-text" class="form-control" v-model.lazy="text"
placeholder="Command input string">
</div>
</div>
</div>
Expand All @@ -222,15 +242,15 @@ <h5 class="mb-1">{{calculateCommandTime + " " + command.template.mnemonic }}</h5
<template id="command-argument-template">
<div>
<label :for="argument.name">{{ argument.name }}</label>
<input v-if="argument.type != 'Enum'" :type="inputType[0]" :id="argument.name" class="form-control"
:placeholder="argument.name" :pattern="inputType[1]"
v-model="argument.value">
<input v-if="argument.type != 'Enum'" :type="inputType[0]" :id="argument.name" class="form-control fprime-input"
:placeholder="argument.name" :pattern="inputType[1]" :step="inputType[2]" v-on:input="validate"
v-model="argument.value" :class="argument.error == '' ? '' : 'is-invalid'" required>
<v-select v-if="argument.type == 'Enum'" :id="argument.name" style="flex: 1 1 auto;"
:clearable="false" :searchable="true"
:clearable="false" :searchable="true" @input="validate"
:filterable="true" label="full_name" :options="argument.possible"
v-model="argument.value">
v-model="argument.value" class="fprime-input" :class="argument.error == '' ? '' : 'is-invalid'" required>
</v-select>
<div>{{ argument.error }}</div>
<div class="invalid-feedback">{{ argument.error }}</div>
</div>
</template>

Expand Down
82 changes: 82 additions & 0 deletions src/fprime_gds/flask/static/js/validate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* validate.js:
*
* Validation functionality for inputs as part of fprime.
*/
let TYPE_LIMITS = {
I8: [-128n, 127n],
U8: [0n, 255n],
I16: [-32768n, 32767n],
U16: [0n, 65535n],
I32: [-2147483648n, 2147483647n],
U32: [0n, 4294967295n],
I64: [-9223372036854775808n, 9223372036854775807n],
U64: [0n, 18446744073709551615n]
};

/**
* Finds a name in a list ignoring case. Will return the name as seen in the list exactly, or null. E.g. abc123 in
* ABC123, hello, 123 would return ABC123. In the case of multiple matches, the first one is returned.
*
* It will first look for exact matches, and return that. Then it will look for singular inexact "differs by case"
* match. If multiple matches are found without an exact match or of no matches are found, null is returned to indicate
* error.
*
* @param token: item to search for regardless of case
* @param possible: list of possible values
* @return {null|*}: matching item from possible or null if not found, or multiple inexact matches.
*/
export function find_case_insensitive(token, possible) {
// Exact match
if (possible.indexOf(token) != -1) {
return token;
}
if (token == null) {
return null
}
token = token.toLowerCase();
let matches = possible.filter(item => {return item.toLowerCase() == token});
if (matches.length == 1) {
return matches[0];
}
return null; // Not exactly one match
}

/**
* Validate an input argument.
* @param argument: argument to validate (will be updated with error)
* @return {boolean}: true if valid, false otherwise
*/
export function validate_input(argument) {
argument.error = "";
// Integral types checking
if (argument.type in TYPE_LIMITS) {
let value = (argument.value == null)? null : BigInt(argument.value);
let limits = TYPE_LIMITS[argument.type];
let message = (argument.type.startsWith("U")) ? "binary, octal, decimal, or hexadecimal unsigned integer":
"signed decimal integer";

if (value == null || value < limits[0] || value > limits[1]) {
argument.error = "Supply " + message + " between " + limits.join(" and ");
return false;
}
}
// Floating point types
else if (argument.type.startsWith("F") && isNaN(parseFloat(argument.value))) {
argument.error = "Supply floating point number";
return false;
}
// Enumeration types
else if ("possible" in argument) {
let valid_arg = find_case_insensitive(argument.value, argument.possible);
if (valid_arg == null) {
argument.error = "Supply one of: " + argument.possible.join(" ");
return false;
} else {
argument.value = valid_arg;
}
} else if (argument.type == "String" && (argument.value == "" || argument.value == null)) {
argument.error = "Supply general text";
}
return true;
}
Loading

0 comments on commit cb8aede

Please sign in to comment.