이 가이드에서는 입력 변수를 검증하는 방법을 설명합니다.
입력 변수를 정의할 때는 사용자에게 적절한 값을 입력하도록 검증하는 것이 좋습니다. 예를 들어 사용자에게 숫자를 입력하도록 요청하는 경우 사용자가 a 대신 1을 입력하는지 확인하면 단계
가 오류 없이 실행되는지 확인할 수 있습니다.
입력 변수를 검증하는 방법에는 두 가지가 있습니다.
- 클라이언트 측 검증: 클라이언트 측 검증을 사용하면 사용자의 기기에서 직접 사용자 입력을 확인할 수 있습니다. 사용자는 즉각적인 의견을 받고 단계를 구성하는 동안 입력의 오류를 수정할 수 있습니다.
- 서버 측 검증: 서버 측 검증을 사용하면 검증 중에 서버에서 로직을 실행할 수 있습니다. 이는 클라이언트에 없는 정보(예: 다른 시스템 또는 데이터베이스의 데이터)를 조회해야 할 때 유용합니다.
클라이언트 측 검증
클라이언트 측 검증을 구현하는 방법에는 두 가지가 있습니다.
- 위젯에 특정 문자 수 미만이 포함되어 있는지 또는
@기호가 포함되어 있는지 확인하는 것과 같은 기본 검증의 경우 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 검증을 구현하여 두 텍스트 입력이 동일한지 확인하는 방법을 보여줍니다. 동일하지 않으면 오류 메시지가 표시됩니다.
-
그림 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 검증 작업
- 산술 연산
+: 두 개의int64,uint64또는double숫자를 더합니다.-: 두 개의int64,uint64또는double숫자를 뺍니다.*: 두 개의int64,uint64, 또는double숫자를 곱합니다./: 두 개의int64,uint64또는double숫자를 나눕니다 (정수 나누기).%: 두 개의int64또는uint64숫자의 모듈로를 계산합니다.-:int64또는uint64숫자를 부정합니다.
- 논리 연산:
&&: 두 개의 불리언 값에 대해 논리AND연산을 실행합니다.||: 두 개의 불리언 값에 대해 논리OR연산을 실행합니다.!: 불리언 값에 대해 논리NOT연산을 실행합니다.
- 비교 연산:
==: 두 값이 동일한지 확인합니다. 숫자 및 목록을 지원합니다.!=: 두 값이 동일하지 않은지 확인합니다. 숫자 및 목록을 지원합니다.<: 첫 번째int64,uint64또는double숫자가 두 번째 숫자보다 작은지 확인합니다.<=: 첫 번째int64,uint64또는double숫자가 두 번째 숫자보다 작거나 같은지 확인합니다.>: 첫 번째int64,uint64, 또는double숫자가 두 번째 숫자보다 큰지 확인합니다.>=: 첫 번째int64,uint64또는double숫자가 두 번째 숫자보다 크거나 같은지 확인합니다.
- 목록 작업:
in: 값이 목록에 있는지 확인합니다. 숫자, 문자열, 중첩 목록을 지원합니다.size: 목록의 항목 수를 반환합니다. 숫자 및 중첩 목록을 지원합니다.
지원되지 않는 CEL 검증 시나리오
- 이항 연산의 잘못된 인수 크기: 이항 연산 (예:
add_int64, 같음)에는 정확히 두 개의 인수가 필요합니다. 다른 수의 인수를 제공하면 오류가 발생합니다. - 단항 연산의 잘못된 인수 크기: 단항 연산 (예:
negate_int64)에는 정확히 하나의 인수가 필요합니다. 다른 수의 인수를 제공하면 오류가 발생합니다. - 숫자 연산에서 지원되지 않는 유형: 숫자 이항 및 단항 연산은 숫자 인수만 허용합니다. 다른 유형 (예: 불리언)을 제공하면 오류가 발생합니다.
서버 측 검증
서버 측 검증을 사용하면 단계의 코드에서 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();
}
}