1 /*
2 * SPDX-FileCopyrightText: none
3 * SPDX-License-Identifier: CC0-1.0
4 */
5
6 package dev.metaschema.core.model.constraint;
7
8 import dev.metaschema.core.datatype.markup.MarkupMultiline;
9 import dev.metaschema.core.metapath.DynamicContext;
10 import dev.metaschema.core.metapath.IMetapathExpression;
11 import dev.metaschema.core.metapath.MetapathException;
12 import dev.metaschema.core.metapath.item.ISequence;
13 import dev.metaschema.core.metapath.item.node.IDefinitionNodeItem;
14 import dev.metaschema.core.model.IAttributable;
15 import dev.metaschema.core.model.IDescribable;
16 import dev.metaschema.core.model.ISource;
17 import dev.metaschema.core.util.ObjectUtils;
18 import edu.umd.cs.findbugs.annotations.NonNull;
19 import edu.umd.cs.findbugs.annotations.Nullable;
20
21 /**
22 * Represents a rule constraining the model of a Metaschema assembly, field or
23 * flag. Provides a common interface for all constraint definitions.
24 */
25 public interface IConstraint extends IAttributable, IDescribable {
26 /**
27 * The type of constraint.
28 */
29 enum Type {
30 /** Constraint restricting the set of allowed values. */
31 ALLOWED_VALUES("allowed-values"),
32 /** Constraint specifying occurrence requirements. */
33 CARDINALITY("cardinality"),
34 /** Constraint expressing an expected condition. */
35 EXPECT("expect"),
36 /** Constraint creating an index over items. */
37 INDEX("index"),
38 /** Constraint requiring uniqueness across items. */
39 UNIQUE("unique"),
40 /** Constraint verifying index key references. */
41 INDEX_HAS_KEY("index-has-key"),
42 /** Constraint validating pattern matching. */
43 MATCHES("matches"),
44 /** Constraint reporting a condition. */
45 REPORT("report");
46
47 @NonNull
48 private final String name;
49
50 Type(@NonNull String name) {
51 this.name = name;
52 }
53
54 /**
55 * Get the name identifier for this constraint type.
56 *
57 * @return the name
58 */
59 @NonNull
60 public String getName() {
61 return name;
62 }
63 }
64
65 /**
66 * The degree to which a constraint violation is significant.
67 * <p>
68 * These values are ordered from least significant to most significant.
69 */
70 enum Level {
71 /**
72 * No violation.
73 */
74 NONE,
75 /**
76 * A violation of the constraint represents a point of interest.
77 */
78 INFORMATIONAL,
79 /**
80 * A violation of the constraint represents a fault in the content that may
81 * warrant review by a developer when performing model or tool development.
82 */
83 DEBUG,
84 /**
85 * A violation of the constraint represents a potential issue with the content.
86 */
87 WARNING,
88 /**
89 * A violation of the constraint represents a fault in the content. This may
90 * include issues around compatibility, integrity, consistency, etc.
91 */
92 ERROR,
93 /**
94 * A violation of the constraint represents a serious fault in the content that
95 * will prevent typical use of the content.
96 */
97 CRITICAL;
98 }
99
100 /**
101 * Get the default level to use if no level is provided.
102 *
103 * @return the default level
104 */
105 @NonNull
106 static Level defaultLevel() {
107 return Level.ERROR;
108 }
109
110 /**
111 * Get the Metapath to use if no target is provided.
112 *
113 * @return the expression
114 */
115 @NonNull
116 static IMetapathExpression defaultTarget() {
117 return IMetapathExpression.contextNode();
118 }
119
120 /**
121 * Get a string that identifies the provided constraint using the most specific
122 * information available.
123 *
124 * @param constraint
125 * the constraint to identify
126 * @return the constraint identification statement
127 */
128 @NonNull
129 static String getConstraintIdentity(@NonNull IConstraint constraint) {
130 String identity;
131 if (constraint.getId() != null) {
132 identity = String.format("with id '%s'", constraint.getId());
133 } else if (constraint.getFormalName() != null) {
134 identity = String.format("with the formal name '%s'", constraint.getFormalName());
135 } else {
136 identity = String.format("targeting '%s'", constraint.getTarget().getPath());
137 }
138 return ObjectUtils.notNull(identity);
139 }
140
141 /**
142 * Get a stable identifier for this constraint, suitable for use in SARIF output
143 * and timing correlation.
144 * <p>
145 * Returns the author-defined {@link #getId()} if present. Otherwise, generates
146 * a deterministic fallback identifier based on the constraint type, source
147 * location, and target expression.
148 *
149 * @return a non-null identifier string
150 */
151 @NonNull
152 default String getInternalIdentifier() {
153 String id = getId();
154 if (id != null) {
155 return id;
156 }
157 // Deterministic fallback based on constraint properties
158 String source = getSource().getLocationHint();
159 String target = getTarget().getPath();
160 int hash = (source + "|" + target).hashCode();
161 return ObjectUtils.notNull(getType().getName() + "-" + String.format("%08x", hash));
162 }
163
164 /**
165 * Get the constraint type.
166 *
167 * @return the constraint type
168 */
169 @NonNull
170 Type getType();
171
172 /**
173 * Retrieve the unique identifier for the constraint.
174 *
175 * @return the identifier or {@code null} if no identifier is defined
176 */
177 @Nullable
178 String getId();
179
180 /**
181 * Get information about the source of the constraint.
182 *
183 * @return the source information
184 */
185 @NonNull
186 ISource getSource();
187
188 /**
189 * The significance of a violation of this constraint.
190 *
191 * @return the level
192 */
193 @NonNull
194 Level getLevel();
195
196 /**
197 * Retrieve the Metapath expression to use to query the targets of the
198 * constraint.
199 *
200 * @return a Metapath expression
201 */
202 @NonNull
203 IMetapathExpression getTarget();
204
205 /**
206 * Based on the provided {@code contextNodeItem}, find all nodes matching the
207 * target expression.
208 *
209 * @param item
210 * the node item to evaluate the target expression against
211 * @param dynamicContext
212 * the Metapath evaluation context to use
213 * @return the matching nodes as a sequence
214 * @throws MetapathException
215 * if an error occurred during evaluation
216 * @see #getTarget()
217 */
218 @NonNull
219 ISequence<? extends IDefinitionNodeItem<?, ?>> matchTargets(
220 @NonNull IDefinitionNodeItem<?, ?> item,
221 @NonNull DynamicContext dynamicContext);
222
223 /**
224 * Retrieve the remarks associated with the constraint.
225 *
226 * @return the remarks or {@code null} if no remarks are defined
227 */
228 MarkupMultiline getRemarks();
229
230 /**
231 * Used for double dispatch supporting the visitor pattern provided by
232 * implementations of {@link IConstraintVisitor}.
233 *
234 * @param <T>
235 * the Java type of a state object passed to the visitor
236 * @param <R>
237 * the Java type of the result returned by the visitor methods
238 * @param visitor
239 * the visitor implementation
240 * @param state
241 * the state object passed to the visitor
242 * @return the visitation result
243 * @see IConstraintVisitor
244 */
245 <T, R> R accept(@NonNull IConstraintVisitor<T, R> visitor, T state);
246 }