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}