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

Interaction input schema check #136

Merged
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
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@
},
"main": "background.js",
"dependencies": {
"@node-wot/binding-coap": "~0.8.1",
"@node-wot/binding-http": "~0.8.1",
"@node-wot/binding-mqtt": "~0.8.1",
"@node-wot/binding-websockets": "~0.8.1",
"@node-wot/core": "~0.8.1",
"@node-wot/binding-coap": "~0.8.5",
"@node-wot/binding-http": "~0.8.5",
"@node-wot/binding-mqtt": "~0.8.5",
"@node-wot/binding-websockets": "~0.8.5",
"@node-wot/core": "~0.8.5",
"ajv": "^8.11.0",
"core-js": "^3.8.3",
"cors-anywhere": "^0.4.4",
Expand Down
33 changes: 9 additions & 24 deletions src/backend/Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,30 +170,23 @@ export async function invokeInteractions(selectedInteractions) {
const resultActions: any[] = [];
const resultEvents: any[] = [];

for (const interaction in selectedInteractions) {
if (!selectedInteractions.hasOwnProperty(interaction)) {
continue;
}

for (const interaction of selectedInteractions) {
const {
interactionName,
interactionSelectBtn,
interactionTitle,
interactionType
} = selectedInteractions[interaction];
} = interaction;

switch (interactionType) {
case PossibleInteractionTypesEnum.PROP_READ:
if (interactionSelectBtn.interaction) {
// Invoke property read (no input)
const resultProp = await selectedInteractions[
interaction
].interactionSelectBtn.interaction();
const resultProp = await interactionSelectBtn.interaction();

resultProps.push({
resultType: PossibleInteractionTypesEnum.PROP_READ,
resultTitle: interactionTitle,
resultValue: resultProp.error ? resultProp.error : (await resultProp.res.value()),
resultValue: resultProp.error ? resultProp.error : resultProp.res,
resultTime: `${resultProp.s} sec ${resultProp.ms} ms`,
resultError: resultProp.error ? true : false,
resultSize: resultProp.size
Expand All @@ -204,9 +197,7 @@ export async function invokeInteractions(selectedInteractions) {
case PossibleInteractionTypesEnum.PROP_WRITE:
if (interactionSelectBtn.interaction) {
// Invoke property write (with input)
const resultProp = await selectedInteractions[
interaction
].interactionSelectBtn.interaction(interactionSelectBtn.input);
const resultProp = await interactionSelectBtn.interaction(interactionSelectBtn.input);

resultProps.push({
resultType: PossibleInteractionTypesEnum.PROP_READ,
Expand All @@ -225,9 +216,7 @@ export async function invokeInteractions(selectedInteractions) {
case PossibleInteractionTypesEnum.PROP_OBSERVE_READ:
case PossibleInteractionTypesEnum.PROP_OBSERVE_WRITE:
if (interactionSelectBtn.interaction) {
const resultProp = await selectedInteractions[
interaction
].interactionSelectBtn.interaction();
const resultProp = await interactionSelectBtn.interaction();

resultProps.push({
resultType: PossibleInteractionTypesEnum.PROP_OBSERVE_READ,
Expand All @@ -241,9 +230,7 @@ export async function invokeInteractions(selectedInteractions) {
case PossibleInteractionTypesEnum.EVENT_SUB:
// Subscribe to event
if (interactionSelectBtn.interaction) {
let resultEvent = await selectedInteractions[
interaction
].interactionSelectBtn.interaction();
let resultEvent = await interactionSelectBtn.interaction();

resultEvents.push({
resultType: PossibleInteractionTypesEnum.EVENT_SUB,
Expand All @@ -263,16 +250,14 @@ export async function invokeInteractions(selectedInteractions) {
case PossibleInteractionTypesEnum.ACTION:
if (interactionSelectBtn.interaction) {
// Invoke action (possibly with input)
const resultAction = await selectedInteractions[
interaction
].interactionSelectBtn.interaction(interactionSelectBtn.input);
const resultAction = await interactionSelectBtn.interaction(interactionSelectBtn.input);

resultActions.push({
resultType: PossibleInteractionTypesEnum.ACTION,
resultTitle: interactionTitle,
resultValue: resultAction.error
? resultAction.error
: (await resultAction.res.value() || 'Success'),
: (resultAction.res || 'Success'),
resultTime: `${resultAction.s} sec ${resultAction.ms} ms`,
resultError: resultAction.error ? true : false,
resultSize: resultAction.size
Expand Down
17 changes: 7 additions & 10 deletions src/backend/TdParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,15 @@ export default class TdParser {
const response = await consumedTd
.readProperty(property)
.then(async res => {
await res;
const endTime = process.hrtime(startTime);
return {
res,
res: await res.value(),
s: endTime[0],
ms: endTime[1] / 1000000,
size: 'n.A.'
};
})
.catch(async err => {
await err;
.catch(err => {
const endTime = process.hrtime(startTime);
return {
res: undefined,
Expand Down Expand Up @@ -144,6 +142,7 @@ export default class TdParser {
btnInputType: this.getCorrectInputType(
this.consumedTd.getThingDescription().properties![property]
),
btnInputSchema: this.consumedTd.getThingDescription().properties![property],
btnKey: `property-${property}-write`,
btnGeneralStyle: 'btn-event-interaction',
btnSelectedStyle: 'btn-event-interaction-selected',
Expand Down Expand Up @@ -178,8 +177,7 @@ export default class TdParser {
size: 'n.A.'
};
})
.catch(async err => {
await err;
.catch(err => {
const endTime = process.hrtime(startTime);
return {
res: undefined,
Expand Down Expand Up @@ -217,6 +215,7 @@ export default class TdParser {
btnInputType: this.getCorrectInputTypeActions(
this.consumedTd.getThingDescription().actions![action]
),
btnInputSchema: this.consumedTd.getThingDescription().actions![action].input,
btnKey: `action-${action}`,
btnGeneralStyle: 'btn-event-interaction',
btnSelectedStyle: 'btn-event-interaction-selected',
Expand All @@ -240,17 +239,15 @@ export default class TdParser {
const response = await consumedTd
.invokeAction(action, input)
.then(async res => {
await res;
const endTime = process.hrtime(startTime);
return {
res: res,
res: await res?.value(),
s: endTime[0],
ms: endTime[1] / 1000000,
size: 'n.A.'
};
})
.catch(async err => {
await err;
.catch(err => {
const endTime = process.hrtime(startTime);
return {
error: err,
Expand Down
4 changes: 2 additions & 2 deletions src/components/01_atoms/aButtonSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
</button>

<div class="select-btn-container">
<img class="select-btn" @click.prevent="changeSelection" :src="!btnDisabled ? currentSrc : srcSelectionNotPossibele"/>
<img class="select-btn" @click.prevent="changeSelection" :src="!btnDisabled ? currentSrc : srcSelectionNotPossible"/>
</div>

</div>
Expand All @@ -44,7 +44,7 @@ export default Vue.extend({
currentSrc: require('@/assets/circle.png'),
srcUnselected: require('@/assets/circle.png'),
srcSelected: require('@/assets/checked_circle.png'),
srcSelectionNotPossibele: require('@/assets/circle_grey.png')
srcSelectionNotPossible: require('@/assets/circle_grey.png')
};
},
props: {
Expand Down
77 changes: 68 additions & 9 deletions src/components/01_atoms/aInteractionInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
type="text"
:placeholder="getPlaceholder"
v-model="inputValue"
@change="changeInput(inputValue, false)"
@input="changeInput(inputValue, false)"
/>

<input
Expand All @@ -18,7 +18,7 @@
:max="this.btnInputType.propMax"
placeholder="Number"
v-model="inputValue"
@change="changeInput(inputValue, false)"
@input="changeInput(inputValue, false)"
/>

<div v-else-if="btnInputType.propType === 'boolean'" class="input-dropdown">
Expand Down Expand Up @@ -66,14 +66,16 @@
<img
class="select-btn"
@click.prevent="btnSelected ? deselect() : select()"
:src="!isInputEmpty ? currentSrc : srcSelectionNotPossibele"
:src="!hasError && !isInputEmpty ? currentSrc : srcSelectionNotPossible"
/>
</div>
</div>
</template>

<script lang="ts">
import Vue from 'vue';
import Ajv from 'ajv';

export default Vue.extend({
name: 'aInteractionInput',
created() {
Expand All @@ -93,7 +95,7 @@ export default Vue.extend({
currentSrc: require('@/assets/circle.png'),
srcUnselected: require('@/assets/circle.png'),
srcSelected: require('@/assets/checked_circle.png'),
srcSelectionNotPossibele: require('@/assets/circle_grey.png')
srcSelectionNotPossible: require('@/assets/circle_grey.png')
};
},
props: {
Expand All @@ -120,6 +122,13 @@ export default Vue.extend({
type: Object,
required: true
},
/**
* Input data shema to validate the user input.
*/
btnInputSchema: {
type: Object,
required: false
},
/**
* Button css style. Can either be 'btn-grey' or 'btn-pos' or any other custom style.
*/
Expand Down Expand Up @@ -185,6 +194,27 @@ export default Vue.extend({
} else {
return false;
}
},
hasError(): boolean {
let errorMessage = '\n';

const validationResult = this.getInputValidationResult();

if (validationResult) {
for (const result of validationResult) {
errorMessage += `- ${result}\n`;
}
}

errorMessage = errorMessage.trim();

if (errorMessage) {
this.$emit('show-error-message', errorMessage);
return true;
} else {
this.$emit('remove-error-message', errorMessage);
return false;
}
}
},
methods: {
Expand Down Expand Up @@ -217,12 +247,16 @@ export default Vue.extend({
return false;
},
changeInput(input: string | boolean | number | any, isDropdown: boolean) {
if (input === undefined) return;
if (input === undefined || input === '' || input === null) {
this.deselect();
return;
}
// Hide dropdown and change input when enum/boolean
if (isDropdown) {
this.dropdownVisible = !this.dropdownVisible;
(this as any).inputValue = input;
}

// When btn is selected emit selection change
if (this.btnSelected) {
this.$emit(
Expand All @@ -235,7 +269,7 @@ export default Vue.extend({
},
select() {
// Cannot be selected when there's no input value
if (this.isInputEmpty || !this.element) return;
if (this.isInputEmpty || !this.element || this.hasError) return;
// Change UI selection
this.btnSelected = true;
this.currentSrc = this.srcSelected;
Expand All @@ -256,6 +290,7 @@ export default Vue.extend({
// Parse string input to correct data type (do not show in UI)
getParsedInputValue() {
let parsedInputValue: any = this.inputValue;

if (
['integer', 'float', 'double', 'number'].indexOf(
this.btnInputType.propType
Expand All @@ -267,7 +302,6 @@ export default Vue.extend({
try {
parsedInputValue = JSON.parse(this.inputValue);
} catch {
// TODO: show error that input is incorrect
parsedInputValue = null;
}
}
Expand All @@ -276,14 +310,39 @@ export default Vue.extend({
try {
parsedInputValue = JSON.parse(jsonArr);
} catch {
// TODO: show error that input is incorrect
parsedInputValue = null;
}
if (!parsedInputValue || typeof parsedInputValue === 'string')
parsedInputValue = this.inputValue.split(' ');
}

return parsedInputValue;
}
},
getInputValidationResult(): any {
if (this.isInputEmpty) {
return;
}

if (this.btnInputSchema) {
const ajv = new Ajv({ allErrors: true, strict: false });
const validate = ajv.compile(this.btnInputSchema);

const valid = validate(this.getParsedInputValue());

if (!valid) {
const errorMessages: (string | undefined) [] = [];
for (const error of validate.errors!) {
if (error.instancePath) {
errorMessages.push(`${error.instancePath} ${error.message}`);
} else {
errorMessages.push(error.message);
}
}

return errorMessages;
}
}
},
}
});
</script>
Expand Down
Loading