davsclaus commented on code in PR #26525: URL: https://github.com/apache/camel/pull/26525#discussion_r4029545259
########## dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/UserManualDocExamplesTest.java: ########## @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.yaml.validator; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.Error; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * The YAML route examples of the user manual must pass the YAML validator (CAMEL-24774), as the examples of the pages + * bundled in the catalog do in {@link EipDocExamplesTest}: the manual is not in the catalog, so its pages are read from + * the source tree when the tests run inside the Camel repository. + * <p/> + * A block is a route example when its first entry is one of the roots of the YAML DSL schema ({@code - route:}, + * {@code - beans:}, {@code - rest:}...); a step written on its own ({@code - setBody:}) or the YAML of another tool is + * not judged. The upgrade and migration guides show the syntax of older releases on purpose and are left out. + */ +class UserManualDocExamplesTest { + + private static final Path PAGES = Path.of("docs", "user-manual", "modules", "ROOT", "pages"); Review Comment: Applied in a168161c20a3, thanks — `-{4,}` as in the XML guard. _Claude Code on behalf of @davsclaus_ ########## dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/UserManualDocExamplesTest.java: ########## @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.yaml.validator; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.Error; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * The YAML route examples of the user manual must pass the YAML validator (CAMEL-24774), as the examples of the pages + * bundled in the catalog do in {@link EipDocExamplesTest}: the manual is not in the catalog, so its pages are read from + * the source tree when the tests run inside the Camel repository. + * <p/> + * A block is a route example when its first entry is one of the roots of the YAML DSL schema ({@code - route:}, + * {@code - beans:}, {@code - rest:}...); a step written on its own ({@code - setBody:}) or the YAML of another tool is + * not judged. The upgrade and migration guides show the syntax of older releases on purpose and are left out. + */ +class UserManualDocExamplesTest { + + private static final Path PAGES = Path.of("docs", "user-manual", "modules", "ROOT", "pages"); + private static final Pattern YAML_BLOCK = Pattern.compile("\\[source,yaml\\]\\n-{4}\\n(.*?)\\n-{4}", Pattern.DOTALL); + private static final Pattern FIRST_KEY = Pattern.compile("^\\s*-\\s+([A-Za-z]+)\\s*:"); + + private static YamlValidator validator; + private static Set<String> roots; + + private record DocExamples(int examples, List<String> failures) { + } + + @BeforeAll + static void setup() throws Exception { + validator = new YamlValidator(); + validator.init(); + roots = new HashSet<>(); + try (InputStream is = YamlValidator.class.getResourceAsStream("/schema/camelYamlDsl.json")) { + JsonNode schema = new ObjectMapper().readTree(is); + schema.path("items").path("properties").fieldNames().forEachRemaining(roots::add); + } + } + + @Test + void everyYamlRouteExampleOfTheUserManualValidates() throws Exception { + Map<String, String> pages = currentPages(); + assumeTrue(!pages.isEmpty(), "the user manual is only checked inside the Camel source tree"); + + DocExamples result = validate(pages); + + assertThat(roots).as("the roots of the YAML DSL schema").contains("route", "beans", "rest", "routeTemplate"); + assertThat(result.examples()).as("YAML route examples found in the user manual").isGreaterThan(150); + assertThat(result.failures()).as("User manual examples that do not validate").isEmpty(); + } + + @Test + void theCheckSeesWhatItIsFor() throws Exception { + String page + = "[source,yaml]\n----\n- route:\n from:\n uri: direct:a\n steps:\n - to:\n uri: mock:b\n----\n" + + "[source,yaml]\n----\n- route:\n templateParameters:\n - name: x\n----\n" + + "[source,yaml]\n----\n- setBody:\n simple: hello\n----\n" + + "[source,yaml]\n----\n- require: '@antora/lunr'\n----\n"; + + DocExamples result = validate(Map.of("fake", page)); + + assertThat(result.examples()).as("the two route examples, not the step or the Antora entry").isEqualTo(2); + assertThat(result.failures()).hasSize(1); + assertThat(result.failures().get(0)).startsWith("fake.adoc:12 example 2:"); + } + + private static DocExamples validate(Map<String, String> pages) throws Exception { + int examples = 0; + List<String> failures = new ArrayList<>(); + for (Map.Entry<String, String> entry : pages.entrySet()) { + String page = entry.getKey(); + String doc = entry.getValue(); + int n = 0; + Matcher m = YAML_BLOCK.matcher(doc); + while (m.find()) { + String yaml = m.group(1).stripTrailing() + "\n"; + Matcher key = FIRST_KEY.matcher(yaml.stripLeading()); + if (!key.find() || !roots.contains(key.group(1))) { + // a step or an option fragment, or the YAML of another tool: not a route file + continue; + } + n++; + examples++; + List<Error> errors = validator.validate(yaml); + if (!errors.isEmpty()) { + int line = 1 + countLines(doc, m.start(1)); + failures.add(page + ".adoc:" + line + " example " + n + ": " + errors.get(0).getMessage()); + } + } + } + return new DocExamples(examples, failures); + } + + /** + * The pages of the user manual that document the current release, by name, or an empty map outside the source tree. + */ + private static Map<String, String> currentPages() throws Exception { + Map<String, String> answer = new TreeMap<>(); + Path dir = Path.of("").toAbsolutePath(); + while (dir != null && !(Files.isDirectory(dir.resolve(PAGES)) && Files.isDirectory(dir.resolve("components")))) { + dir = dir.getParent(); + } + if (dir == null) { + return answer; Review Comment: Kept as two copies (different modules, no shared test-support module between camel-catalog and camel-yaml-dsl-validator, and it is four lines), with a comment on each pointing at the other so a change to the criteria is made in both — a168161c20a3. _Claude Code on behalf of @davsclaus_ ########## catalog/camel-catalog/src/test/java/org/apache/camel/catalog/DocExamplesXmlSchemaTest.java: ########## @@ -0,0 +1,387 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.catalog; + +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import org.xml.sax.InputSource; +import org.xml.sax.SAXParseException; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * The XML examples of the documentation bundled in the catalog must validate against the XML schemas bundled next to + * them (CAMEL-24774): a {@code <camelContext>} against camel-spring.xsd as Spring XML validates it, the xml-io roots + * ({@code <camel>}, {@code <routes>}...) against camel-xml-io.xsd, and any other Camel element written on its own + * ({@code <route>}, {@code <onException>}, {@code <marshal>}...) as the global element it is in camel-spring.xsd, so a + * fragment is checked without a wrapper imposing an element order the page never meant. + * <p/> + * The examples are what people copy: a Camel 1.x namespace, an attribute renamed in Camel 3, a tag closed by another + * tag or a {@code &} in a URI fails the build here instead of in the reader's application. + */ +class DocExamplesXmlSchemaTest { + + private static final Pattern XML_BLOCK = Pattern.compile("\\[source,xml\\]\\n-{4,}\\n(.*?)\\n-{4,}", Pattern.DOTALL); + private static final Pattern CALLOUT = Pattern.compile("\\s*<\\d+>\\s*$", Pattern.MULTILINE); + private static final Pattern XML_DECLARATION = Pattern.compile("<\\?xml[^>]*\\?>"); + private static final Pattern CAMEL_CONTEXT + = Pattern.compile("<(\\w+:)?camelContext\\b.*?</(\\w+:)?camelContext>", Pattern.DOTALL); + /** + * An element that makes a block a Camel example rather than a payload, a Maven POM, an XSLT or a configuration file + * of another project which happens to share element names with the Camel schema. + */ + private static final Pattern CAMEL_SIGNATURE = Pattern.compile("<(camelContext|routes|route|from|to|toD|" + + "marshal|unmarshal|dataFormats|rest|restConfiguration|" + + "routeTemplate|templatedRoute|routeConfiguration|" + + "onException|errorHandler|intercept|interceptFrom|" + + "interceptSendToEndpoint|onCompletion|setHeader|setBody|" + + "setProperty|setVariable|choice|filter|split|aggregate|" + + "multicast|recipientList|wireTap|log|process|transform|" + + "convertBodyTo|validate|enrich|pollEnrich|threads|threadPool|" + + "propertyPlaceholder|globalOptions|streamCaching|jmxAgent|" + + "sslContextParameters|xpath|simple|csimple|jsonpath|jq|" + + "xtokenize|tokenize|xquery|groovy|datasonnet|mvel|ognl)" + + "[\\s/>]"); + + private static final String SPRING_NS = "http://camel.apache.org/schema/spring"; + private static final String XML_IO_NS = "http://camel.apache.org/schema/xml-io"; + private static final String SPRING_BEANS_NS = "http://www.springframework.org/schema/beans"; + + /** The roots of an xml-io file, validated against camel-xml-io.xsd. */ + private static final Set<String> XML_IO_ROOTS + = Set.of("camel", "routes", "rests", "routeTemplates", "templatedRoutes", "routeConfigurations"); + + /** + * Top-level elements that are not judged on their own: a Spring {@code <bean>} and its {@code <property>} share + * their names with Camel elements, and the rest are Maven, XSLT or payload samples. + */ + private static final Set<String> IGNORED_ROOTS = Set.of("bean", "beans", "property", "dependency", "dependencies", + "dependencyManagement", "plugin", "plugins", "build", "project", "extensions"); + + /** + * Examples the schemas cannot judge, by page and a text found in the example: an elided {@code <beans ...>} start + * tag, a Spring bean next to route fragments, and the {@code <namespace>} child of an expression, which the model + * and the xml-io parser support but no generated schema can express next to the expression text (JAXB + * {@code @XmlValue}), so Spring XML declares the namespaces as {@code xmlns:} attributes instead; and the endpoint + * page's unescaped {@code &}, which is there to show the error it causes. + */ + private static final Map<String, String> EXAMPLES_SKIPPED = Map.of( + "xmlsecurity-sign-component", "<bean id=\"xadesProperties\"", + "xmlsecurity-verify-component", "<bean id=\"xadesProperties\"", + "spring-summary", "<beans xmlns=\"http://www.springframework.org/schema/beans\"", + "split-eip", "<namespace key=", + "xtokenize-language", "<namespace key=", + "endpoint", "paramA=1¶mB=2"); + + private static CamelCatalog catalog; + private static Schema springSchema; + private static Schema xmlIoSchema; + private static Set<String> springGlobalElements; + + private record DocExamples(int examples, List<String> failures) { + } + + @BeforeAll + static void setup() throws Exception { + catalog = new DefaultCamelCatalog(); + SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + springSchema = factory.newSchema(new StreamSource(new StringReader(catalog.springSchemaAsXml()))); + xmlIoSchema = factory.newSchema(new StreamSource(new StringReader(catalog.xmlIoSchemaAsXml()))); + springGlobalElements = globalElements(catalog.springSchemaAsXml()); + } + + @Test + void everyXmlExampleOfTheDocumentationValidates() throws Exception { + List<String> pages = new ArrayList<>(catalog.findDocNames()); + + DocExamples result = validate(pages); + + assertTrue(result.examples() > 1500, "XML examples found in the documentation: " + result.examples()); + assertTrue(result.failures().isEmpty(), + "Documentation XML examples that do not validate:\n " + String.join("\n ", result.failures())); + } + + @Test + void everyXmlExampleOfTheUserManualValidates() throws Exception { + Map<String, String> pages = UserManualPages.currentPages(); + assumeTrue(!pages.isEmpty(), "the user manual is only checked inside the Camel source tree"); + + DocExamples result = validate(pages); + + assertTrue(result.examples() > 200, "XML examples found in the user manual: " + result.examples()); + assertTrue(result.failures().isEmpty(), + "User manual XML examples that do not validate:\n " + String.join("\n ", result.failures())); + } + + @Test + void theCheckSeesWhatItIsFor() { + // the classes of mistakes the check exists for + assertTrue(validateBlock("<camelContext xmlns=\"http://activemq.apache.org/camel/schema/spring\">\n" + + "<route><from uri=\"direct:a\"/><to uri=\"mock:b\"/></route></camelContext>") + .toString().contains("camelContext"), "the Camel 1.x namespace"); + assertTrue(!validateBlock("<from>seda:a?size=1</from>").isEmpty(), "a from with text content"); + assertTrue(!validateBlock("<onCompletion executorServiceRef=\"x\"><to uri=\"mock:a\"/></onCompletion>").isEmpty(), + "a Camel 2 attribute"); + assertTrue(!validateBlock("<route><from uri=\"direct:a\"/><to uri=\"mock:b?x=1&y=2\"/></route>").isEmpty(), + "an unescaped ampersand"); + assertTrue(!validateBlock("<route><from uri=\"direct:a\"/><marshal><jaxb/></marshal></route>").isEmpty(), + "a missing required attribute"); + assertTrue(!validateBlock("<onException><redeliveryPolicy maximumRedeliveries=\"1\"/>" + + "<exception>java.io.IOException</exception></onException>") + .isEmpty(), + "the element order Spring XML enforces"); + // and what it leaves alone + assertTrue(validateBlock("<route id=\"a\"><from uri=\"direct:a\"/><to uri=\"mock:b\"/></route>\n" + + "<onException><exception>java.io.IOException</exception><to uri=\"mock:e\"/></onException>") + .isEmpty(), "valid fragments in any order"); + assertTrue(validateBlock("<routes><route><from uri=\"direct:a\"/><to uri=\"mock:b\"/></route></routes>").isEmpty(), + "an xml-io root"); + assertTrue(validateBlock("<mapper><delete id=\"x\" parameterType=\"int\">delete from t</delete></mapper>").isEmpty(), + "a MyBatis mapper sharing element names"); + assertTrue(validateBlock("<bean id=\"a\" class=\"com.foo.A\"/>\n<from uri=\"direct:a\"/> <1>\n...").isEmpty(), + "a Spring bean, a callout and a placeholder line"); + } + + @Test + void theSkippedPagesStillExist() throws Exception { + Map<String, String> manual = UserManualPages.pages(); + for (String page : EXAMPLES_SKIPPED.keySet()) { + assertTrue(catalog.asciiDoc(page) != null || manual.containsKey(page), + "the skipped page " + page + " is gone, drop the entry"); + } + } + + private static DocExamples validate(List<String> pages) throws Exception { + Map<String, String> docs = new LinkedHashMap<>(); + for (String page : pages) { + String doc = catalog.asciiDoc(page); + if (doc != null) { + docs.put(page, doc); + } + } + return validate(docs); + } + + private static DocExamples validate(Map<String, String> pages) { + int examples = 0; + List<String> failures = new ArrayList<>(); + for (Map.Entry<String, String> entry : pages.entrySet()) { + String page = entry.getKey(); + String doc = entry.getValue(); + int n = 0; + Matcher m = XML_BLOCK.matcher(doc); + while (m.find()) { + n++; + String xml = m.group(1); + String skipped = EXAMPLES_SKIPPED.get(page); + if (skipped != null && xml.contains(skipped)) { + continue; + } + int line = 1 + countLines(doc, m.start(1)); + for (String error : validateBlock(xml)) { + failures.add(page + ".adoc:" + line + " example " + n + ": " + error); + } + examples++; + } + } + return new DocExamples(examples, failures); + } + + /** + * Validates one XML example and returns what is wrong with it (empty when it validates or is not an example the + * schemas can judge). + */ + static List<String> validateBlock(String xml) { + List<String> errors = new ArrayList<>(); + String text = clean(xml); + if (text.isBlank() || !CAMEL_SIGNATURE.matcher(text).find()) { + // a payload, a POM, an XSLT: not something the Camel schemas describe + return errors; + } + if (text.contains("<camelContext") || text.contains(":camelContext")) { + Matcher cm = CAMEL_CONTEXT.matcher(text); + while (cm.find()) { + String context = cm.group(0); + if (cm.group(1) != null) { + String prefix = cm.group(1).substring(0, cm.group(1).length() - 1); + if (!context.contains("xmlns:" + prefix + "=")) { + context = context.replaceFirst("<" + prefix + ":camelContext", + "<" + prefix + ":camelContext xmlns:" + prefix + "=\"" + SPRING_NS + "\""); + } + } else { + context = withDefaultNamespace(context, "camelContext", SPRING_NS); + } + validate(context, springSchema, errors); + } + return errors; + } + + List<Element> roots; + try { + roots = topLevelElements(text); + } catch (SAXParseException e) { + errors.add("not well-formed XML: " + e.getMessage()); + return errors; + } catch (Exception e) { + errors.add("not well-formed XML: " + e); + return errors; + } + for (Element root : roots) { + String name = root.getNodeName(); + String local = name.contains(":") ? name.substring(name.indexOf(':') + 1) : name; + String ns = root.getAttribute("xmlns"); + if (SPRING_BEANS_NS.equals(ns) || IGNORED_ROOTS.contains(local) || name.contains(":")) { + // a Spring beans file, a Maven or payload sample, or a prefixed element of another schema + continue; + } + String element = serialize(root); + if (element == null) { + // an undeclared prefix (prop:, camel:...) the page declares elsewhere, nothing to judge + continue; + } + if (XML_IO_ROOTS.contains(local)) { + validate(withDefaultNamespace(element, local, XML_IO_NS), xmlIoSchema, errors); + } else if (springGlobalElements.contains(local)) { + validate(withDefaultNamespace(element, local, SPRING_NS), springSchema, errors); + } + } + return errors; + } + + private static void validate(String document, Schema schema, List<String> errors) { + try { + Validator validator = schema.newValidator(); + validator.validate(new StreamSource(new StringReader(document))); + } catch (SAXParseException e) { + String msg = e.getMessage(); + if (msg != null && msg.contains("is not bound")) { + // a fragment using a prefix its page declares elsewhere (camel:, prop:, xsl:...), nothing to judge + return; + } + errors.add(msg); + } catch (Exception e) { + errors.add(e.toString()); + } + } + + /** Drops the XML declaration, the placeholder lines and the AsciiDoc callouts, which are not part of the XML. */ + static String clean(String xml) { + String text = XML_DECLARATION.matcher(xml).replaceAll(""); + text = CALLOUT.matcher(text).replaceAll(""); + StringBuilder sb = new StringBuilder(); + for (String line : text.split("\n")) { + String t = line.trim(); + if (t.equals("...") || t.equals("…")) { + continue; + } + sb.append(line).append('\n'); + } + return sb.toString(); + } + + /** Adds the default namespace to the root element when the example does not declare one. */ + private static String withDefaultNamespace(String document, String root, String ns) { + int end = document.indexOf('>'); + String startTag = end > 0 ? document.substring(0, end) : document; + if (startTag.contains("xmlns=")) { + return document; + } + return document.replaceFirst("<" + root + "\\b", "<" + root + " xmlns=\"" + ns + "\""); + } + + /** The top-level elements of a fragment, parsed without namespaces so undeclared prefixes do not matter. */ + private static List<Element> topLevelElements(String fragment) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(false); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document dom = builder.parse(new InputSource(new StringReader("<wrap>" + fragment + "</wrap>"))); + List<Element> answer = new ArrayList<>(); + NodeList children = dom.getDocumentElement().getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + if (child.getNodeType() == Node.ELEMENT_NODE) { + answer.add((Element) child); + } + } + return answer; + } + + private static String serialize(Element element) { + try { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + StringWriter sw = new StringWriter(); + transformer.transform(new DOMSource(element), new StreamResult(sw)); + return sw.toString(); + } catch (Exception e) { + // an attribute with a prefix the fragment does not declare cannot be serialized + return null; + } + } + + /** The names of the global elements of a schema: the elements an example can start with. */ + private static Set<String> globalElements(String xsd) { + Set<String> answer = new HashSet<>(); + Matcher m = Pattern.compile("\n <xs:element name=\"([A-Za-z-]+)\"").matcher(xsd); Review Comment: Agreed, that was silently-skip-everything territory. In a168161c20a3 the global elements are collected from the parsed XSD (`xs:element` children of `xs:schema`), `globalElements()` throws when the set comes back empty, and `theCheckSeesWhatItIsFor` asserts the set is the real one (200+ names including `route`, `onException`, `marshal`). _Claude Code on behalf of @davsclaus_ -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
