Validar uma variável de entrada

Este guia explica como validar uma variável de entrada.

Ao definir uma variável de entrada, como prática recomendada, valide se o usuário insere um valor adequado. Por exemplo, se você pedir ao usuário para inserir um numeral, verificar se ele insere 1 em vez de a confirma que a etapa é executada sem erros.

Há duas maneiras de validar uma variável de entrada:

  • Validação do lado do cliente: Com a validação do lado do cliente, você verifica a entrada do usuário diretamente no dispositivo. O usuário recebe feedback imediato e pode corrigir erros na entrada ao configurar a etapa.
  • Validação do lado do servidor: a validação do lado do servidor permite executar a lógica no servidor durante a validação, o que é útil quando você precisa pesquisar informações que o cliente não tem, como dados em outros sistemas ou bancos de dados.

Validação do lado do cliente

Há duas maneiras de implementar a validação do lado do cliente:

  • Para validação básica, como verificar se um widget contém menos de um determinado número de caracteres ou contém o símbolo @, invoque a classe Validation do serviço de card do complemento do Google Workspace.
  • Para validação robusta, como comparar valores de widget com outros valores de widget, você pode adicionar a validação da Linguagem de expressão comum (CEL) aos seguintes widgets de card compatíveis usando CardService.

Invocar a classe Validation

O exemplo a seguir valida se um widget TextInput contém 10 ou menos caracteres:

Apps Script

const validation = CardService.newValidation().setCharacterLimit('10').setInputType(
    CardService.InputType.TEXT);

Para outras opções de validação, use a validação CEL.

Validação CEL

A validação da Common Expression Language (CEL) oferece verificações de entrada instantâneas sem a latência da validação do lado do servidor, descarregando as verificações de valor de entrada que não dependem da pesquisa de dados de outros serviços para o lado do cliente.

Você também pode usar a CEL para criar comportamentos de card, como mostrar ou ocultar um widget dependendo do resultado da validação. Esse tipo de comportamento é útil para mostrar ou ocultar uma mensagem de erro que ajuda os usuários a corrigir as entradas.

A criação de uma validação CEL completa envolve os seguintes componentes:

  • ExpressionData no card: contém a lógica de validação especificada e a lógica de acionamento do widget quando uma das condições definidas é atendida.

    • Id: um identificador exclusivo para o ExpressionData no card atual.
    • Expression: a string CEL que define a lógica de validação (por exemplo, "value1 == value2").
    • Conditions: uma lista de condições que contém uma seleção de resultados de validação predefinidos (SUCESSO ou FALHA). As condições são vinculadas ao EventAction do lado do widget por Triggers com um actionRuleId compartilhado.
    • EventAction no nível do card: ativa as validações CEL no card e associa o campo ExpressionData aos widgets de resultado por acionadores pós-evento.
      • actionRuleId: ID exclusivo para esse EventAction.
      • ExpressionDataAction: definido como START_EXPRESSION_EVALUATION para indicar que essa ação inicia a avaliação CEL.
      • Trigger: conecta as Conditions a EventActions do lado do widget com base no actionRuleId.
  • EventAction no nível do widget: controla o comportamento do widget de resultado quando a condição de sucesso ou falha é atendida. Um widget de resultado, por exemplo, pode ser um TextParagraph que contém uma mensagem de erro que só fica visível quando a validação falha.

    • actionRuleId: corresponde ao actionRuleId no Trigger do lado do card.
    • CommonWidgetAction: define ações que não envolvem avaliações, como atualizar a visibilidade do widget.
      • UpdateVisibilityAction: uma ação que atualiza o estado de visibilidade de um widget (VISÍVEL ou OCULTO).

O exemplo a seguir demonstra como implementar a validação CEL para verificar se duas entradas de texto são iguais. Uma mensagem de erro será mostrada se elas não forem iguais.

  • Quando a failCondition é atendida (as entradas não são iguais), o widget de mensagem de erro é definido como VISIBLE e aparece.
    Figura 1: Quando a failCondition é atendida (as entradas não são iguais), o widget de mensagem de erro é definido como VISIBLE e aparece.
  • Quando a condição de sucesso é atendida (as entradas são iguais), o widget de mensagem de erro é definido como HIDDEN e não aparece.
    Figura 2: quando a successCondition é atendida (as entradas são iguais), o widget de mensagem de erro é definido como HIDDEN e não aparece.

Confira o exemplo de código do aplicativo e o arquivo de manifesto 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();
}

Arquivo de manifesto 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"
          }
        }
      ]
    }
  }
}

Widgets e operações de validação CEL compatíveis

Widgets de card que aceitam a validação CEL

Os seguintes widgets aceitam a validação CEL:

  • TextInput
  • SelectionInput
  • DateTimePicker

Operações de validação CEL compatíveis

  • Operações aritméticas
    • +: adiciona dois números int64, uint64 ou double.
    • -: subtrai dois números int64, uint64 ou double.
    • *: multiplica dois números int64, uint64 ou double.
    • /: divide dois números int64, uint64, ou double (divisão inteira).
    • %: calcula o módulo de dois int64 ou uint64 números.
    • -: nega um número int64 ou uint64.
  • Operações lógicas:
    • &&: executa uma operação lógica AND em dois valores booleanos.
    • ||: executa uma operação lógica OR em dois valores booleanos.
    • !: executa uma operação lógica NOT em um valor booleano.
  • Operações de comparação:
    • ==: verifica se dois valores são iguais. Aceita números e listas.
    • !=: verifica se dois valores são diferentes. Aceita números e listas.
    • <: verifica se o primeiro número int64, uint64 ou double é menor que o segundo.
    • <=: verifica se o primeiro número int64, uint64 ou double é menor ou igual ao segundo.
    • >: verifica se o primeiro número int64, uint64 ou double é maior que o segundo.
    • >=: verifica se o primeiro número int64, uint64 ou double é maior ou igual ao segundo.
  • Operações de lista:
    • in: verifica se um valor está presente em uma lista. Aceita números, strings e listas aninhadas.
    • size: retorna o número de itens em uma lista. Aceita números e listas aninhadas.

Cenários de validação CEL não aceitos

  • Tamanhos de argumentos incorretos para operações binárias: as operações binárias (por exemplo, add_int64, equals) exigem exatamente dois argumentos. Fornecer um número diferente de argumentos vai gerar um erro.
  • Tamanhos de argumentos incorretos para operações unárias: as operações unárias (por exemplo, negate_int64) exigem exatamente um argumento. Fornecer um número diferente de argumentos vai gerar um erro.
  • Tipos não aceitos em operações numéricas: as operações binárias e unárias numéricas só aceitam argumentos numéricos. Fornecer outros tipos (por exemplo, booleanos) vai gerar um erro.

Validação do lado do servidor

Com a validação do lado do servidor, você pode executar a lógica do lado do servidor especificando a onSaveFunction() no código da etapa. Quando o usuário sai do card de configuração da etapa, onSaveFunction() é executado e permite verificar a entrada do usuário.

Se a entrada do usuário for válida, retorne saveWorkflowAction.

Se a entrada do usuário for inválida, retorne um card de configuração que mostre uma mensagem de erro ao usuário explicando como resolver o erro.

Como a validação do lado do servidor é assíncrona, o usuário pode não saber sobre o erro de entrada até publicar o fluxo.

O id de cada entrada validada no arquivo de manifesto precisa corresponder ao name de um widget de card no código.

O exemplo a seguir valida se uma entrada de texto do usuário inclui o sinal "@":

Arquivo de manifesto

O trecho do arquivo de manifesto especifica uma onSaveFunction() chamada "onSave":

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"
          }
        }
      ]
    }
  }
}

Código do aplicativo

O código da etapa inclui uma função chamada onSave(). Ela valida se uma string inserida pelo usuário inclui @. Se isso acontecer, ela salva a etapa. Caso contrário, ela retorna um card de configuração com uma mensagem de erro explicando como corrigir o erro.

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();
  }
}