入力変数を検証する

このガイドでは、入力変数を検証する方法について説明します。

入力変数を定義する際は、ベスト プラクティスとして、ユーザーが適切な値を入力したことを検証します。たとえば、ユーザーに数値を入力するよう求める場合、a ではなく 1 が入力されたことを確認することで、ステップがエラーなく実行されることを確認できます。

入力変数を検証する方法は 2 つあります。

  • クライアントサイドの検証: クライアントサイドの検証では、ユーザーの入力をデバイス上で直接検証します。ユーザーは即座にフィードバックを受け取り、ステップの構成中に、入力のエラーを修正できます。
  • サーバーサイド検証: サーバーサイド検証では、検証中にサーバーでロジックを実行できます。これは、他のシステムやデータベースのデータなど、クライアントが持っていない情報を検索する必要がある場合に便利です。

クライアントサイドの検証

クライアントサイドの検証を実装する方法は 2 つあります。

  • ウィジェットに特定の文字数より少ない文字が含まれているか、@ 記号が含まれているかなどの基本的な検証を行うには、Google Workspace アドオンの Card サービスの Validation クラスを呼び出します。
  • ウィジェットの値を他のウィジェットの値と比較するなど、堅牢な検証を行う場合は、CardService を使用して、次のサポートされているカード ウィジェットに Common Expression Language(CEL)検証を追加できます。

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: カード側の TriggeractionRuleId に一致します。
    • CommonWidgetAction: ウィジェットの表示の更新など、評価を伴わないアクションを定義します。
      • UpdateVisibilityAction: ウィジェットの可視性状態(VISIBLE または HIDDEN)を更新するアクション。

次の例は、2 つのテキスト入力が等しいかどうかを確認するために CEL 検証を実装する方法を示しています。等しくない場合は、エラー メッセージが表示されます。

  • failCondition が満たされると(入力が等しくない)、エラー メッセージ ウィジェットが VISIBLE に設定され、表示されます。
    図 1: failCondition が満たされる(入力が等しくない)と、エラー メッセージ ウィジェットが VISIBLE に設定され、表示されます。
  • successCondition が満たされる(入力が等しい)と、エラー メッセージ ウィジェットは HIDDEN に設定され、表示されなくなります。
    図 2: successCondition が満たされる(入力が等しい)と、エラー メッセージ ウィジェットが HIDDEN に設定され、表示されません。

アプリケーション コードと JSON マニフェスト ファイルの例を次に示します。

Apps Script

function onConfig() {

  // Create a Card
  let card = CardService.newCardBuilder();

  const textInput_1 = CardService.newTextInput()
    .setTitle("Input number 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 number 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("Two number equals")
    .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("The first and second value must match.")
    .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": "actionElement",
          "state": "ACTIVE",
          "name": "CEL Demo",
          "description": "Demonstrates CEL Validation",
          "workflowAction": {
            "inputs": [
              {
                "id": "value1",
                "description": "The first number",
                "cardinality": "SINGLE",
                "dataType": {
                  "basicType": "INTEGER"
                }
              },
              {
                "id": "value2",
                "description": "The second number",
                "cardinality": "SINGLE",
                "dataType": {
                  "basicType": "INTEGER"
                }
              }
            ],
            "onConfigFunction": "onConfig",
            "onExecuteFunction": "onExecute"
          }
        }
      ]
    }
  }
}

サポートされている CEL 検証ウィジェットとオペレーション

CEL 検証をサポートするカード ウィジェット

次のウィジェットは CEL 検証をサポートしています。

  • TextInput
  • SelectionInput
  • DateTimePicker

サポートされている CEL 検証オペレーション

  • 算術演算
    • +: 2 つの int64uint64、または double の数値を加算します。
    • -: 2 つの int64uint64double の数値を減算します。
    • *: 2 つの int64uint64double の数値を乗算します。
    • /: 2 つの int64uint64double の数値を割ります(整数除算)。
    • %: 2 つの int64 または uint64 の数値の剰余を計算します。
    • -: int64 または uint64 の数値を否定します。
  • 論理演算:
    • &&: 2 つのブール値に対して論理 AND 演算を行います。
    • ||: 2 つのブール値に対して論理 OR 演算を行います。
    • !: ブール値に対して論理 NOT 演算を行います。
  • 比較オペレーション:
    • ==: 2 つの値が等しいかどうかを確認します。番号とリストをサポートしています。
    • !=: 2 つの値が等しくないかどうかを確認します。番号とリストをサポートしています。
    • <: 最初の int64uint64double の数値が 2 番目の数値より小さいかどうかを確認します。
    • <=: 最初の int64uint64double の数値が 2 番目の数値以下かどうかを確認します。
    • >: 最初の int64uint64double の数値が 2 番目の数値より大きいかどうかを確認します。
    • >=: 最初の int64uint64double の数値が 2 番目の数値以上かどうかを確認します。
  • オペレーションの一覧表示:
    • in: 値がリストに含まれているかどうかを確認します。数値、文字列、ネストされたリストをサポートしています。
    • size: リスト内のアイテム数を返します。数字とネストされたリストをサポートしています。

サポートされていない CEL 検証シナリオ

  • バイナリ オペレーションの引数のサイズが正しくない: バイナリ オペレーション(add_int64、等しいなど)には、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": "actionElement",
          "state": "ACTIVE",
          "name": "Calculate",
          "description": "Asks the user for an email address",
          "workflowAction": {
            "inputs": [
              {
                "id": "email",
                "description": "email address",
                "cardinality": "SINGLE",
                "required": true,
                "dataType": {
                  "basicType": "STRING"
                }
              }
            ],
            "onConfigFunction": "onConfigCalculate",
            "onExecuteFunction": "onExecuteCalculate",
            "onSaveFunction": "onSave"
          }
        }
      ]
    }
  }
}

アプリケーション コード

このステップのコードには、onSave() という関数が含まれています。ユーザーが入力した文字列に @ が含まれていることを検証します。含まれている場合は、フローのステップを保存します。そうでない場合は、エラーを修正する方法を説明するエラー メッセージを含む構成カードが返されます。

Apps Script

/**
 * Validates user input asynchronously when the user
 * navigates away from a step's configuration card.
*/
function onSave(event) {

  // "email" matches the input ID specified in the manifest file.
  var email = event.workflow.actionInvocation.inputs["email"];

  // Validate that the email address contains an "@" sign:
  if(email.includes("@")) {

  // If successfully validated, save and proceed.
    return {
      "hostAppAction" : {
        "workflowAction" : {
          "saveWorkflowAction" : {}
        }
      }
    };

  // If the input is invalid, return a card with an error message
  } else {

var card = {
    "sections": [
      {
        "header": "Collect Email",
        "widgets": [
          {
            "textInput": {
              "name": "email",
              "label": "email address",
              "hostAppDataSource" : {
                "workflowDataSource" : {
                  "includeVariables" : true
                }
              }
            }
          },
          {
            "textParagraph": {
              "text": "<b>Error:</b> Email addresses must include the '@' sign.",
              "maxLines": 1
            }
          }
        ]
      }
    ]
  };
  return pushCard(card);
  }
}