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

lestarch: command raw text input and fixed client validation #9

Merged
merged 1 commit into from
Jul 26, 2021
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
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 message 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