001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.commons.beanquery; 018 019import org.apache.juneau.commons.*; 020 021/** 022 * Thrown when a {@code $}-search expression names an unknown operator or is otherwise malformed (an unclosed 023 * parenthesis, a wrong argument count, junk after the close paren, an empty expression, and so on), or when a 024 * higher-level check (column allow-list, sort keys, paging, regex policy, operator/type mismatch, an unparseable 025 * typed value) rejects a caller-supplied query. 026 * 027 * <p> 028 * The single public parser ({@link SearchExpressionParser}) throws this for every syntactic problem; the #1/#2 029 * validation pipeline throws it for every semantic one. Every instance carries a stable {@link Code}: messages are 030 * for people, {@link #code()} is for callers (an HTTP error body, the JS engine, a test corpus) that need to branch 031 * on *which* problem occurred without parsing the message. 032 * 033 * <h5 class='section'>Example:</h5> 034 * <p class='bjava'> 035 * <jk>try</jk> { 036 * SearchExpressionParser.<jsm>parse</jsm>(<js>"$foo(1)"</js>, SearchOperatorSet.<jsm>standard</jsm>()); 037 * } <jk>catch</jk> (BeanQuerySyntaxException e) { 038 * <jk>if</jk> (e.code() == BeanQuerySyntaxException.Code.<jsf>UNKNOWN_OPERATOR</jsf>) 039 * log.warn(<js>"Unknown operator: {}"</js>, e.getMessage()); 040 * } 041 * </p> 042 * 043 * @serial exclude 044 * @since 10.0.0 045 */ 046public class BeanQuerySyntaxException extends BasicRuntimeException { 047 048 private static final long serialVersionUID = 1L; 049 050 /** 051 * Stable identity for a {@link BeanQuerySyntaxException}, independent of the (free-text) message. 052 * 053 * <p> 054 * Codes are part of the public API: renaming one is a breaking change. New codes may be added. 055 * 056 * @since 10.0.0 057 */ 058 public enum Code { 059 060 /** The search string is empty or blank. */ 061 EMPTY_EXPRESSION, 062 /** {@code $} + letter-led text before {@code (} that is not a valid operator name (e.g. {@code $eq!(x)}). */ 063 INVALID_OPERATOR_NAME, 064 /** An operator name with no {@code (...)} call following it. */ 065 MALFORMED_OPERATOR, 066 /** The operator name is well-formed but not registered in the operator set in use. */ 067 UNKNOWN_OPERATOR, 068 /** The operator's argument count is outside its declared {@code minArgs}/{@code maxArgs}. */ 069 BAD_ARG_COUNT, 070 /** A comma-separated argument slot is empty (a stray or trailing comma). */ 071 EMPTY_ARGUMENT, 072 /** An operator call, or a #2 group, never closes. */ 073 UNBALANCED, 074 /** Text follows the closing {@code )} of a top-level operator call. */ 075 TRAILING_TEXT, 076 /** A quoted literal never finds its closing quote. */ 077 UNTERMINATED_QUOTE, 078 /** A {@code $or}/{@code $and}/{@code $not} group has too few or too many items. */ 079 GROUP_ARITY, 080 /** The same column appears more than once at the top level of a search string. */ 081 DUPLICATE_COLUMN, 082 /** The raw search string exceeds the configured maximum length. */ 083 SEARCH_TOO_LONG, 084 /** The raw options string exceeds the configured maximum length. */ 085 OPTS_TOO_LONG, 086 /** The search string has more top-level clauses than the configured maximum. */ 087 TOO_MANY_CLAUSES, 088 /** A search, sort, or view column is not in the allow-list. */ 089 UNKNOWN_COLUMN, 090 /** An expression nests deeper than the configured maximum. */ 091 TOO_DEEP, 092 /** {@code $regex} was used but regex search is not enabled for this list. */ 093 REGEX_DISABLED, 094 /** A {@code $regex} pattern exceeds the configured maximum length. */ 095 REGEX_TOO_LONG, 096 /** A {@code $regex} match exceeded its evaluation deadline. */ 097 REGEX_TIMEOUT, 098 /** An operator does not apply to the column's declared {@code SearchType}. */ 099 OPERATOR_TYPE, 100 /** The sort clause names more keys than the configured maximum. */ 101 TOO_MANY_SORT_KEYS, 102 /** A requested paging position is negative. */ 103 NEGATIVE_POSITION, 104 /** A literal argument or bare literal is not a parseable {@code numeric}, {@code boolean} or {@code timestamp} value for its column's type (see {@link SearchType#parse(String)}). */ 105 BAD_VALUE; 106 } 107 108 private final Code code; 109 110 /** 111 * Constructor. 112 * 113 * @param code The stable {@link Code} identifying which problem occurred. Must not be <jk>null</jk>. 114 * @param message The {@link String#format(String, Object...) String.format}-style message (<c>%s</c> placeholders). 115 * @param args Optional {@link String#format(String, Object...) String.format}-style arguments. 116 */ 117 public BeanQuerySyntaxException(Code code, String message, Object...args) { 118 super(message, args); 119 this.code = code; 120 } 121 122 /** 123 * Returns the stable code identifying which problem occurred. 124 * 125 * @return The code, never <jk>null</jk>. 126 */ 127 public Code code() { 128 return code; 129 } 130}