Skip to content
Draft
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
148 changes: 148 additions & 0 deletions src/action-affordance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import InteractionAffordance from './interaction-affordance.js';
import ValidationError from './validation-error.js';

/** @import {DataSchema, PartialActionDescription, ActionDescription, Form} from "./types.js" */

/**
* Action Affordance
*
* Represents an ActionAffordance from the W3C WoT Thing Description 1.1
* specification https://www.w3.org/TR/wot-thing-description/#actionaffordance
*/
class ActionAffordance extends InteractionAffordance {
/**
* @type {DataSchema|undefined}
*/
input;

/**
* @type {DataSchema|undefined}
*/
output;

/**
* @type {boolean|undefined}
*/
safe;

/**
* @type {boolean|undefined}
*/
idempotent;

/**
* @type {boolean|undefined}
*/
synchronous;

/**
* Create a new Action.
*
* @param {string} name The name of the ActionAffordance from its
* key in an actions Map.
* @param {PartialActionDescription} metadata Metadata describing an
* ActionAffordance from a partial Thing Description.
*/
constructor(name, metadata) {
super(name, metadata);

let validationError = new ValidationError([]);

// Parse safe member
try {
this.#parseSafeMember(metadata.safe);
} catch (error) {
validationError.merge(error);
}

// Parse idempotent member
try {
this.#parseIdempotentMember(metadata.idempotent);
} catch (error) {
validationError.merge(error);
}

// TODO: Parse other members
}

/**
* Parse safe member.
*
* @param {boolean|undefined} safe
*
* TODO: Consider omitting this member if not specified
*/
#parseSafeMember(safe) {
// Throw an error if not a boolean or undefined
if (!(safe === undefined || typeof safe == 'boolean')) {
throw new ValidationError([
{
field: `actions.${this.name}.safe`,
description: 'safe member is not a boolean',
},
]);
}
// If undefined then default to false
if (safe === undefined) {
this.safe = false;
// Otherwise set the provided value
} else {
this.safe = safe;
}
}

/**
* Parse idempotent member.
*
* @param {boolean|undefined} idempotent
*
* TODO: Consider omitting this member if not specified
*/
#parseIdempotentMember(idempotent) {
// Throw an error if not a boolean or undefined
if (!(idempotent === undefined || typeof idempotent == 'boolean')) {
throw new ValidationError([
{
field: `actions.${this.name}.idempotent`,
description: 'idempotent member is not a boolean',
},
]);
}
// If undefined then default to false
if (idempotent === undefined) {
this.idempotent = false;
// Otherwise set the provided value
} else {
this.idempotent = idempotent;
}
}

/**
* Set invoke handler function.
*
* @param {(value: any) => Promise<void>} handler An asynchronous function to action invocations.
*/
setInvokeHandler(handler) {
this.invokeHandler = handler;
}

/**
* Invoke the action.
*
* @param {any} input The input to the action.
* @returns {Promise<any>} A Promise which resolves with the output of the action.
*/
async invoke(input) {
// TODO
}

/**
* @returns {ActionDescription}
*/
getMetadata() {
//TODO
return { forms: [{ href: 'http://localhost' }] };
}
}

export default ActionAffordance;
54 changes: 42 additions & 12 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,19 @@
// TODO: Constrain set of possible values for op

/**
* Property Description
*
* @typedef {{
* '@type'?: string|Array<string>,
* title?: string|undefined,
* description?: string|undefined,
* title?: string,
* description?: string
* }} InteractionDescription
*/

/**
* Property Description
*
* @typedef {InteractionDescription & DataSchema & {
* forms: Array<Form>,
* readOnly?: boolean,
* writeOnly?: boolean
* observeable?: boolean|undefined
* }} PropertyDescription
*/

Expand All @@ -84,16 +88,40 @@
*
* Same as PropertyDescription but forms is optional
*
* @typedef {{
* '@type'?: string|Array<string>,
* title?: string|undefined,
* description?: string|undefined,
* @typedef {InteractionDescription & DataSchema & {
* forms?: Array<Form>,
* readOnly?: boolean,
* writeOnly?: boolean
* observeable?: boolean|undefined
* }} PartialPropertyDescription
*/

/**
* Action Description
*
* @typedef { InteractionDescription & {
* forms: Array<Form>,
* input?: DataSchema,
* output?: DataSchema,
* safe?: boolean,
* idempotent?: boolean,
* synchronous?: boolean,
* }} ActionDescription
*/

/**
* Partial Action Description
*
* Same as ActionDescription but forms is optional
*
* @typedef { InteractionDescription & {
* forms?: Array<Form>,
* input?: DataSchema,
* output?: DataSchema,
* safe?: boolean,
* idempotent?: boolean,
* synchronous?: boolean,
* }} PartialActionDescription
*/

/**
* Thing Description
*
Expand All @@ -105,6 +133,7 @@
* description?: string,
* base?: string,
* properties?: Record<string, PropertyDescription>,
* actions?: Record<string, ActionDescription>,
* security: string|Array<string>,
* securityDefinitions: string|Record<string, object>
* }} ThingDescription
Expand All @@ -124,6 +153,7 @@
* description?: string,
* base?: string,
* properties?: Record<string, PartialPropertyDescription>,
* actions?: Record<string, ActionDescription>,
* security?: string|Array<string>,
* securityDefinitions?: string|Record<string, object>
* }} PartialThingDescription
Expand Down
13 changes: 13 additions & 0 deletions src/validation-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ class ValidationError extends Error {
super(...params);
this.validationErrors = validationErrors;
}

/**
* Merge a list of validation errors into this ValidationError.
*
* @param {any} error
*/
merge(error) {
if (error instanceof ValidationError) {
this.validationErrors.push(...error.validationErrors);
} else {
throw error;
}
}
}

export default ValidationError;
Loading