このガイドでは、入力変数を検証する方法について説明します。
入力変数を定義する場合は、ユーザーが適切な値を入力していることを検証することをおすすめします。たとえば、ユーザーに数値を入力してもらう場合、a ではなく 1 が入力されていることを確認することで、ステップがエラーなく実行されることを検証できます。
入力変数を検証する方法は 2 つあります。
- クライアントサイド検証: クライアントサイド検証では、ユーザーのデバイスでユーザーの入力を直接検証します。 ユーザーはすぐにフィードバックを受け取り、ステップの構成中に、入力のエラーを修正できます。
- サーバーサイド検証: サーバーサイド検証では、検証中にサーバーでロジックを実行できます。 これは、クライアントが持っていない 情報(他のシステムやデータベースのデータなど)を検索する必要がある場合に便利です。
クライアントサイド検証
クライアントサイド検証を実装する方法は 2 つあります。
- ウィジェットに特定の文字数未満の文字が含まれているか、
@記号が含まれているかを確認するなど、基本的な検証を行う場合は、Google Workspace アドオンのカード サービスのValidationクラスを呼び出します。 - ウィジェットの値を他のウィジェットの値と比較するなど、堅牢な検証を行う場合は、Common Expression Language(CEL)検証を次のサポートされているカード ウィジェットに
追加できます。
CardService
Validation クラスを呼び出す
次の例では、TextInput ウィジェットに 10 文字以下の文字が含まれていることを検証します。
Apps Script
const validation = CardService.newValidation().setCharacterLimit('10').setInputType(
CardService.InputType.TEXT);
その他の検証オプションについては、CEL 検証を使用してください。
CEL 検証
Common Expression Language(CEL)検証では、他のサービスからのデータの検索に依存しない入力 値のチェックをクライアントサイドにオフロードすることで、サーバーサイド検証のレイテンシなしで入力 値を即座にチェックできます。
CEL を使用して、検証の結果に応じてウィジェットを表示または非表示にするなど、カードの動作を作成することもできます。このような動作は、ユーザーが入力を修正するのに役立つエラー メッセージを表示または非表示にする場合に便利です。
完全な CEL 検証を構築するには、次のコンポーネントが必要です。
カード内の
ExpressionData: 定義された条件のいずれかが満たされたときに、指定された検証ロジックとウィジェットのトリガー ロジックが含まれます。Id: 現在のカード内のExpressionDataの一意の識別子。Expression: 検証ロジックを定義する CEL 文字列(例:"value1 == value2")。Conditions: 事前定義された検証結果(SUCCESS または FAILURE)の選択を含む条件のリスト。条件は、共有のactionRuleIdを持つTriggersを介して、ウィジェット側のEventActionに関連付けられます。- カードレベルの
EventAction: カードで CEL 検証を有効にし、イベント後のトリガーを介してExpressionDataフィールドを結果ウィジェットに関連付けます。actionRuleId: このEventActionの一意の ID。ExpressionDataAction: このアクションが CEL 評価を開始することを示すSTART_EXPRESSION_EVALUATIONに設定します。Trigger:actionRuleIdに基づいて、Conditionsをウィジェット側のEventActionsに接続します。
ウィジェットレベルの
EventAction: 成功または失敗の条件が満たされたときに、結果ウィジェットの動作を制御します。たとえば、結果ウィジェットは、検証が失敗した場合にのみ表示されるエラー メッセージを含むTextParagraphにすることができます。actionRuleId: カード側のTriggerのactionRuleIdと一致します。CommonWidgetAction: ウィジェットの表示の更新など、評価を伴わないアクションを定義します。UpdateVisibilityAction: ウィジェットの表示状態(VISIBLE または HIDDEN)を更新するアクション。
次の例は、CEL 検証を実装して 2 つのテキスト入力が等しいかどうかを確認する方法を示しています。等しくない場合は、エラー メッセージが表示されます。
-
図 1: failConditionが満たされると(入力が等しくない場合)、エラー メッセージ ウィジェットがVISIBLEに設定され、表示されます。 -
図 2: successConditionが満たされると(入力が等しい場合)、エラー メッセージ ウィジェットがHIDDENに設定され、表示されません。
次のコードサンプルと JSON マニフェストは、次のことを示しています。
Apps Script
function onConfig() {
// Create a Card
let cardBuilder = CardService.newCardBuilder();
const textInput_1 = CardService.newTextInput()
.setTitle("Input field 1")
.setFieldName("value1"); // FieldName's value must match a corresponding ID defined in the inputs[] array in the manifest file.
const textInput_2 = CardService.newTextInput()
.setTitle("Input field 2")
.setFieldName("value2"); // FieldName's value must match a corresponding ID defined in the inputs[] array in the manifest file.
let sections = CardService.newCardSection()
.setHeader("Enter same values for the two input fields")
.addWidget(textInput_1)
.addWidget(textInput_2);
// CEL Validation
// Define Conditions
const condition_success = CardService.newCondition()
.setActionRuleId("CEL_TEXTINPUT_SUCCESS_RULE_ID")
.setExpressionDataCondition(
CardService.newExpressionDataCondition()
.setConditionType(
CardService.ExpressionDataConditionType.EXPRESSION_EVALUATION_SUCCESS));
const condition_fail = CardService.newCondition()
.setActionRuleId("CEL_TEXTINPUT_FAILURE_RULE_ID")
.setExpressionDataCondition(
CardService.newExpressionDataCondition()
.setConditionType(
CardService.ExpressionDataConditionType.EXPRESSION_EVALUATION_FAILURE));
// Define Card-side EventAction
const expressionDataAction = CardService.newExpressionDataAction()
.setActionType(
CardService.ExpressionDataActionType.START_EXPRESSION_EVALUATION);
// Define Triggers for each Condition respectively
const trigger_success = CardService.newTrigger()
.setActionRuleId("CEL_TEXTINPUT_SUCCESS_RULE_ID");
const trigger_failure = CardService.newTrigger()
.setActionRuleId("CEL_TEXTINPUT_FAILURE_RULE_ID");
const eventAction = CardService.newEventAction()
.setActionRuleId("CEL_TEXTINPUT_EVALUATION_RULE_ID")
.setExpressionDataAction(expressionDataAction)
.addPostEventTrigger(trigger_success)
.addPostEventTrigger(trigger_failure);
// Define ExpressionData for the current Card
const expressionData = CardService.newExpressionData()
.setId("expData_id")
.setExpression("value1 == value2") // CEL expression
.addCondition(condition_success)
.addCondition(condition_fail)
.addEventAction(eventAction);
card = card.addExpressionData(expressionData);
// Create Widget-side EventActions and a widget to display error message
const widgetEventActionFail = CardService.newEventAction()
.setActionRuleId("CEL_TEXTINPUT_FAILURE_RULE_ID")
.setCommonWidgetAction(
CardService.newCommonWidgetAction()
.setUpdateVisibilityAction(
CardService.newUpdateVisibilityAction()
.setVisibility(
CardService.Visibility.VISIBLE)));
const widgetEventActionSuccess = CardService.newEventAction()
.setActionRuleId("CEL_TEXTINPUT_SUCCESS_RULE_ID")
.setCommonWidgetAction(
CardService.newCommonWidgetAction()
.setUpdateVisibilityAction(
CardService.newUpdateVisibilityAction()
.setVisibility(
CardService.Visibility.HIDDEN)));
const errorWidget = CardService.newTextParagraph()
.setText("<font color=\"#FF0000\"><b>Error:</b> Please enter the same values for both input fields.</font>")
.setVisibility(CardService.Visibility.HIDDEN) // Initially hidden
.addEventAction(widgetEventActionFail)
.addEventAction(widgetEventActionSuccess);
sections = sections.addWidget(errorWidget);
card = card.addSection(sections);
// Build and return the Card
return card.build();
}
JSON マニフェスト ファイル
{
"timeZone": "America/Los_Angeles",
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"addOns": {
"common": {
"name": "CEL validation example",
"logoUrl": "https://www.gstatic.com/images/branding/productlogos/calculator_search/v1/web-24dp/logo_calculator_search_color_1x_web_24dp.png",
"useLocaleFromApp": true
},
"flows": {
"workflowElements": [
{
"id": "cel_validation_demo",
"state": "ACTIVE",
"name": "CEL Demo",
"description": "Demonstrates CEL Validation",
"workflowAction": {
"inputs": [
{
"id": "value1",
"description": "The first number",
"cardinality": "SINGLE",
"dataType": {
"basicType": "STRING"
}
},
{
"id": "value2",
"description": "The second number",
"cardinality": "SINGLE",
"dataType": {
"basicType": "STRING"
}
}
],
"onConfigFunction": "onConfig",
"onExecuteFunction": "onExecute"
}
}
]
}
}
}
サポートされている CEL 検証ウィジェットとオペレーション
CEL 検証をサポートするカード ウィジェット
次のウィジェットは CEL 検証をサポートしています。
TextInputSelectionInputDateTimePicker
サポートされている CEL 検証オペレーション
- 算術演算
+: 2 つのint64、uint64、またはdoubleの数値を加算します。-: 2 つのint64、uint64、またはdoubleの数値を減算します。*:2 つのint64、uint64、またはdoubleの数値を乗算します。/: 2 つのint64、uint64、またはdoubleの数値を割ります(整数除算)。%: 2 つのint64またはuint64の数値の剰余を計算します。-:int64またはuint64の数値を否定します。
- 論理演算:
&&: 2 つのブール値に対して論理AND演算を行います。||: 2 つのブール値に対して論理OR演算を行います。!: ブール値に対して論理NOT演算を行います。
- 比較演算:
==: 2 つの値が等しいかどうかを確認します。数値とリストをサポートします。!=: 2 つの値が等しくないかどうかを確認します。数値とリストをサポートします。<: 最初のint64、uint64、またはdoubleの数値が 2 番目の数値より小さいかどうかを確認します。<=: 最初のint64、uint64、またはdoubleの数値が 2 番目の数値以下かどうかを確認します。>: 最初のint64、uint64、またはdoubleの数値が 2 番目の数値より大きいかどうかを確認します。>=: 最初のint64、uint64、またはdoubleの数値が 2 番目の数値以上かどうかを確認します。
- リスト オペレーション:
in: 値がリストに存在するかどうかを確認します。数値、文字列、ネストされたリストをサポートします。size: リスト内のアイテム数を返します。数値とネストされたリストをサポートします。
サポートされていない CEL 検証シナリオ
- 二項演算の引数のサイズが正しくない: 二項演算(
add_int64、equals など)には、2 つの引数が必要です。引数の数が異なる場合は、エラーがスローされます。 - 単項演算の引数のサイズが正しくない: 単項演算(
negate_int64など)には、1 つの引数が必要です。引数の数が異なる場合は、エラーがスローされます。 - 数値演算でサポートされていない型: 数値の二項演算と単項演算では、数値の引数のみが受け入れられます。他の型(ブール値など)を指定すると、エラーがスローされます。
サーバーサイド検証
サーバーサイド検証では、ステップのコードで onSaveFunction を指定することで、サーバーサイド ロジックを実行できます。ユーザーがステップの構成カードから移動すると、onSaveFunction が実行され、ユーザーの入力を検証できます。
ユーザーの入力が有効な場合は、saveWorkflowAction を返します。
ユーザーの入力が無効な場合は、エラーの解決方法を説明するエラー メッセージを表示する構成カードを返します。
サーバーサイド検証は非同期であるため、ユーザーはフローを公開するまで入力エラーに気づかない可能性があります。
マニフェスト ファイル内の検証済み入力の id は、コード内のカード ウィジェットの name と一致する必要があります。
次の例では、ユーザーのテキスト入力に「@」記号が含まれていることを検証します。
マニフェスト ファイル
マニフェスト ファイルの抜粋では、「onSave」という名前の onSaveFunction を指定しています。
JSON
{
"timeZone": "America/Los_Angeles",
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"addOns": {
"common": {
"name": "Server-side validation example",
"logoUrl": "https://www.gstatic.com/images/branding/productlogos/calculator_search/v1/web-24dp/logo_calculator_search_color_1x_web_24dp.png",
"useLocaleFromApp": true
},
"flows": {
"workflowElements": [
{
"id": "server_validation_demo",
"state": "ACTIVE",
"name": "Email address validation",
"description": "Asks the user for an email address",
"workflowAction": {
"inputs": [
{
"id": "email",
"description": "email address",
"cardinality": "SINGLE",
"required": true,
"dataType": {
"basicType": "STRING"
}
}
],
"onConfigFunction": "onConfig",
"onExecuteFunction": "onExecute",
"onSaveFunction": "onSave"
}
}
]
}
}
}
アプリケーション コード
ステップのコードには、onSave という関数が含まれています。ユーザーが入力した文字列に @ が含まれていることを検証します。含まれている場合は、ステップを保存します。含まれていない場合は、エラーの修正方法を説明するエラー メッセージを含む構成カードを返します。
Apps Script
// A helper method to push a card interface
function pushCard(card) {
const navigation = AddOnsResponseService.newNavigation()
.pushCard(card);
const action = AddOnsResponseService.newAction()
.addNavigation(navigation);
return AddOnsResponseService.newRenderActionBuilder()
.setAction(action)
.build();
}
function onConfig() {
const emailInput = CardService.newTextInput()
.setFieldName("email")
.setTitle("User e-mail")
.setId("email");
const saveButton = CardService.newTextButton()
.setText("Save!")
.setOnClickAction(
CardService.newAction()
.setFunctionName('onSave')
)
const sections = CardService.newCardSection()
.setHeader("Server-side validation")
.setId("section_1")
.addWidget(emailInput)
.addWidget(saveButton);
let card = CardService.newCardBuilder()
.addSection(sections)
.build();
return pushCard(card);
}
function onExecute(event) {
}
/**
* Validates user input asynchronously when the user
* navigates away from a step's configuration card.
*/
function onSave(event) {
console.log(JSON.stringify(event, null, 2));
// "email" matches the input ID specified in the manifest file.
var email = event.formInputs["email"][0];
console.log(JSON.stringify(email, null, 2));
// Validate that the email address contains an "@" sign:
if (email.includes("@")) {
// If successfully validated, save and proceed.
const hostAppAction = AddOnsResponseService.newHostAppAction()
.setWorkflowAction(
AddOnsResponseService.newSaveWorkflowAction()
);
const textDeletion = AddOnsResponseService.newRemoveWidget()
.setWidgetId("errorMessage");
const modifyAction = AddOnsResponseService.newAction()
.addModifyCard(
AddOnsResponseService.newModifyCard()
.setRemoveWidget(textDeletion)
);
return AddOnsResponseService.newRenderActionBuilder()
.setHostAppAction(hostAppAction)
.setAction(modifyAction)
.build();
} else {
// If the input is invalid, return a card with an error message
const textParagraph = CardService.newTextParagraph()
.setId("errorMessage")
.setMaxLines(1)
.setText("<font color=\"#FF0000\"><b>Error:</b> Email addresses must include the '@' sign.</font>");
const emailInput = CardService.newTextInput()
.setFieldName("email")
.setTitle("User e-mail")
.setId("email");
const saveButton = CardService.newTextButton()
.setText("Save!")
.setOnClickAction(
CardService.newAction().setFunctionName('onSave')
)
const sections = CardService.newCardSection()
.setHeader("Server-side validation")
.setId("section_1")
.addWidget(emailInput)
.addWidget(textParagraph) //Insert the error message
.addWidget(saveButton);
let card = CardService.newCardBuilder()
.addSection(sections)
.build();
const navigation = AddOnsResponseService.newNavigation()
.pushCard(card);
const action = AddOnsResponseService.newAction()
.addNavigation(navigation);
const hostAppAction = AddOnsResponseService.newHostAppAction()
.setWorkflowAction(
AddOnsResponseService.newWorkflowValidationErrorAction()
.setSeverity(AddOnsResponseService.ValidationErrorSeverity.CRITICAL)
);
return AddOnsResponseService.newRenderActionBuilder()
.setHostAppAction(hostAppAction)
.setAction(action)
.build();
}
}