xref: /aosp_15_r20/external/json-schema-validator/doc/yaml-line-numbers.md (revision 78c4dd6aa35290980cdcd1623a7e337e8d021c7c)
1*78c4dd6aSAndroid Build Coastguard Worker# Obtaining YAML Line Numbers
2*78c4dd6aSAndroid Build Coastguard Worker
3*78c4dd6aSAndroid Build Coastguard Worker## Scenario 1 - finding YAML line numbers from the JSON tree
4*78c4dd6aSAndroid Build Coastguard Worker
5*78c4dd6aSAndroid Build Coastguard WorkerA great feature of json-schema-validator is it's ability to validate YAML documents against a JSON Scheme. The manner in which this is done though, by pre-processing the YAML into a tree of [JsonNode](https://fasterxml.github.io/jackson-databind/javadoc/2.10/com/fasterxml/jackson/databind/JsonNode.html) objects, breaks the connection back to the original YAML source file. Very commonly, once the YAML has been validated against the schema, there may be additional processing and checking for semantic or content errors or inconsistency in the JSON tree. From an end user point of view, the ideal is to report such errors using line and column references back to the original YAML, but this information is not readily available from the processed JSON tree.
6*78c4dd6aSAndroid Build Coastguard Worker
7*78c4dd6aSAndroid Build Coastguard Worker### Scenario 1, solution part 1 - capturing line details during initial parsing
8*78c4dd6aSAndroid Build Coastguard Worker
9*78c4dd6aSAndroid Build Coastguard WorkerOne solution is to use a custom [JsonNodeFactory](https://fasterxml.github.io/jackson-databind/javadoc/2.10/com/fasterxml/jackson/databind/node/JsonNodeFactory.html) that returns custom JsonNode objects which are created during initial parsing, and which record the original YAML locations that were being parsed at the time they were created. The example below shows this
10*78c4dd6aSAndroid Build Coastguard Worker
11*78c4dd6aSAndroid Build Coastguard Worker```java
12*78c4dd6aSAndroid Build Coastguard Worker    public static class MyNodeFactory extends JsonNodeFactory
13*78c4dd6aSAndroid Build Coastguard Worker    {
14*78c4dd6aSAndroid Build Coastguard Worker        YAMLParser yp;
15*78c4dd6aSAndroid Build Coastguard Worker
16*78c4dd6aSAndroid Build Coastguard Worker        public MyNodeFactory(YAMLParser yp)
17*78c4dd6aSAndroid Build Coastguard Worker        {
18*78c4dd6aSAndroid Build Coastguard Worker            super();
19*78c4dd6aSAndroid Build Coastguard Worker            this.yp = yp;
20*78c4dd6aSAndroid Build Coastguard Worker        }
21*78c4dd6aSAndroid Build Coastguard Worker
22*78c4dd6aSAndroid Build Coastguard Worker        public ArrayNode arrayNode()
23*78c4dd6aSAndroid Build Coastguard Worker        {
24*78c4dd6aSAndroid Build Coastguard Worker            return new MyArrayNode(this, yp.getTokenLocation(), yp.getCurrentLocation());
25*78c4dd6aSAndroid Build Coastguard Worker        }
26*78c4dd6aSAndroid Build Coastguard Worker
27*78c4dd6aSAndroid Build Coastguard Worker        public BooleanNode booleanNode(boolean v)
28*78c4dd6aSAndroid Build Coastguard Worker        {
29*78c4dd6aSAndroid Build Coastguard Worker            return new MyBooleanNode(v, yp.getTokenLocation(), yp.getCurrentLocation());
30*78c4dd6aSAndroid Build Coastguard Worker        }
31*78c4dd6aSAndroid Build Coastguard Worker
32*78c4dd6aSAndroid Build Coastguard Worker        public NumericNode numberNode(int v)
33*78c4dd6aSAndroid Build Coastguard Worker        {
34*78c4dd6aSAndroid Build Coastguard Worker            return new MyIntNode(v, yp.getTokenLocation(), yp.getCurrentLocation());
35*78c4dd6aSAndroid Build Coastguard Worker        }
36*78c4dd6aSAndroid Build Coastguard Worker
37*78c4dd6aSAndroid Build Coastguard Worker        public NullNode nullNode()
38*78c4dd6aSAndroid Build Coastguard Worker        {
39*78c4dd6aSAndroid Build Coastguard Worker            return new MyNullNode(yp.getTokenLocation(), yp.getCurrentLocation());
40*78c4dd6aSAndroid Build Coastguard Worker        }
41*78c4dd6aSAndroid Build Coastguard Worker
42*78c4dd6aSAndroid Build Coastguard Worker        public ObjectNode objectNode()
43*78c4dd6aSAndroid Build Coastguard Worker        {
44*78c4dd6aSAndroid Build Coastguard Worker            return new MyObjectNode(this, yp.getTokenLocation(), yp.getCurrentLocation());
45*78c4dd6aSAndroid Build Coastguard Worker        }
46*78c4dd6aSAndroid Build Coastguard Worker
47*78c4dd6aSAndroid Build Coastguard Worker        public TextNode textNode(String text)
48*78c4dd6aSAndroid Build Coastguard Worker        {
49*78c4dd6aSAndroid Build Coastguard Worker            return (text != null) ? new MyTextNode(text, yp.getTokenLocation(), yp.getCurrentLocation()) : null;
50*78c4dd6aSAndroid Build Coastguard Worker        }
51*78c4dd6aSAndroid Build Coastguard Worker    }
52*78c4dd6aSAndroid Build Coastguard Worker```
53*78c4dd6aSAndroid Build Coastguard Worker
54*78c4dd6aSAndroid Build Coastguard WorkerThe example above includes a basic, but usable subset of all possible JsonNode types - if your YAML needs them, than you should also consider the others i.e. `byte`, `byte[]`, `raw`, `short`, `long`, `float`, `double`, `BigInteger`, `BigDecimal`
55*78c4dd6aSAndroid Build Coastguard Worker
56*78c4dd6aSAndroid Build Coastguard WorkerThere are some important other things to note from the example:
57*78c4dd6aSAndroid Build Coastguard Worker
58*78c4dd6aSAndroid Build Coastguard Worker* Even in a reduced set, `ObjectNode` and `NullNode` should be included
59*78c4dd6aSAndroid Build Coastguard Worker* The current return for methods that receive a null parameter value seems to be null rather than `NullNode` (based on inspecting the underlying `valueOf()` methods in the various `JsonNode` sub classes). Hence the implementation of the `textNode()` method above.
60*78c4dd6aSAndroid Build Coastguard Worker
61*78c4dd6aSAndroid Build Coastguard WorkerThe actual work here is really being done by the YAMLParser - it holds the location of the token being parsed, and the current location in the file. The first of these gives us a line and column number we can use to flag where an error or problem was found, and the second (if needed) can let us calculate a span to the end of the error e.g. if we wanted to highlight or underline the text in error.
62*78c4dd6aSAndroid Build Coastguard Worker
63*78c4dd6aSAndroid Build Coastguard Worker### Scenario 1, solution part 2 - augmented `JsonNode` subclassess
64*78c4dd6aSAndroid Build Coastguard Worker
65*78c4dd6aSAndroid Build Coastguard WorkerWe can be as simple or fancy as we like in the `JsonNode` subclassses, but basically we need 2 pieces of information from them:
66*78c4dd6aSAndroid Build Coastguard Worker
67*78c4dd6aSAndroid Build Coastguard Worker* An interface so when we are post processing the JSON tree, we can recognize nodes that retain line number information
68*78c4dd6aSAndroid Build Coastguard Worker* An interface that lets us extract the relevant location information
69*78c4dd6aSAndroid Build Coastguard Worker
70*78c4dd6aSAndroid Build Coastguard WorkerThose could be the same thing of course, but in our case we separated them as shown in the following example
71*78c4dd6aSAndroid Build Coastguard Worker
72*78c4dd6aSAndroid Build Coastguard Worker```java
73*78c4dd6aSAndroid Build Coastguard Worker    public interface LocationProvider
74*78c4dd6aSAndroid Build Coastguard Worker    {
75*78c4dd6aSAndroid Build Coastguard Worker        LocationDetails getLocationDetails();
76*78c4dd6aSAndroid Build Coastguard Worker    }
77*78c4dd6aSAndroid Build Coastguard Worker
78*78c4dd6aSAndroid Build Coastguard Worker    public interface LocationDetails
79*78c4dd6aSAndroid Build Coastguard Worker    {
80*78c4dd6aSAndroid Build Coastguard Worker        default int getLineNumber()     { return 1; }
81*78c4dd6aSAndroid Build Coastguard Worker        default int getColumnNumber()   { return 1; }
82*78c4dd6aSAndroid Build Coastguard Worker        default String getFilename()    { return ""; }
83*78c4dd6aSAndroid Build Coastguard Worker    }
84*78c4dd6aSAndroid Build Coastguard Worker
85*78c4dd6aSAndroid Build Coastguard Worker    public static class LocationDetailsImpl implements LocationDetails
86*78c4dd6aSAndroid Build Coastguard Worker    {
87*78c4dd6aSAndroid Build Coastguard Worker        final JsonLocation currentLocation;
88*78c4dd6aSAndroid Build Coastguard Worker        final JsonLocation tokenLocation;
89*78c4dd6aSAndroid Build Coastguard Worker
90*78c4dd6aSAndroid Build Coastguard Worker        public LocationDetailsImpl(JsonLocation tokenLocation, JsonLocation currentLocation)
91*78c4dd6aSAndroid Build Coastguard Worker        {
92*78c4dd6aSAndroid Build Coastguard Worker            this.tokenLocation = tokenLocation;
93*78c4dd6aSAndroid Build Coastguard Worker            this.currentLocation = currentLocation;
94*78c4dd6aSAndroid Build Coastguard Worker        }
95*78c4dd6aSAndroid Build Coastguard Worker
96*78c4dd6aSAndroid Build Coastguard Worker        @Override
97*78c4dd6aSAndroid Build Coastguard Worker        public int getLineNumber()      { return (tokenLocation != null) ? tokenLocation.getLineNr() : 1; };
98*78c4dd6aSAndroid Build Coastguard Worker        @Override
99*78c4dd6aSAndroid Build Coastguard Worker        public int getColumnNumber()    { return (tokenLocation != null) ? tokenLocation.getColumnNr() : 1; };
100*78c4dd6aSAndroid Build Coastguard Worker        @Override
101*78c4dd6aSAndroid Build Coastguard Worker        public String getFilename()     { return (tokenLocation != null) ? tokenLocation.getSourceRef().toString() : ""; };
102*78c4dd6aSAndroid Build Coastguard Worker    }
103*78c4dd6aSAndroid Build Coastguard Worker
104*78c4dd6aSAndroid Build Coastguard Worker    public static class MyNullNode extends NullNode implements LocationProvider
105*78c4dd6aSAndroid Build Coastguard Worker    {
106*78c4dd6aSAndroid Build Coastguard Worker        final LocationDetails locDetails;
107*78c4dd6aSAndroid Build Coastguard Worker
108*78c4dd6aSAndroid Build Coastguard Worker        public MyNullNode(JsonLocation tokenLocation, JsonLocation currentLocation)
109*78c4dd6aSAndroid Build Coastguard Worker        {
110*78c4dd6aSAndroid Build Coastguard Worker            super();
111*78c4dd6aSAndroid Build Coastguard Worker            locDetails = new LocationDetailsImpl(tokenLocation, currentLocation);
112*78c4dd6aSAndroid Build Coastguard Worker        }
113*78c4dd6aSAndroid Build Coastguard Worker
114*78c4dd6aSAndroid Build Coastguard Worker        @Override
115*78c4dd6aSAndroid Build Coastguard Worker        public LocationDetails getLocationDetails()
116*78c4dd6aSAndroid Build Coastguard Worker        {
117*78c4dd6aSAndroid Build Coastguard Worker            return locDetails;
118*78c4dd6aSAndroid Build Coastguard Worker        }
119*78c4dd6aSAndroid Build Coastguard Worker    }
120*78c4dd6aSAndroid Build Coastguard Worker
121*78c4dd6aSAndroid Build Coastguard Worker    public static class MyTextNode extends TextNode implements LocationProvider
122*78c4dd6aSAndroid Build Coastguard Worker    {
123*78c4dd6aSAndroid Build Coastguard Worker        final LocationDetails locDetails;
124*78c4dd6aSAndroid Build Coastguard Worker
125*78c4dd6aSAndroid Build Coastguard Worker        public MyTextNode(String v, JsonLocation tokenLocation, JsonLocation currentLocation)
126*78c4dd6aSAndroid Build Coastguard Worker        {
127*78c4dd6aSAndroid Build Coastguard Worker            super(v);
128*78c4dd6aSAndroid Build Coastguard Worker            locDetails = new LocationDetailsImpl(tokenLocation, currentLocation);
129*78c4dd6aSAndroid Build Coastguard Worker        }
130*78c4dd6aSAndroid Build Coastguard Worker
131*78c4dd6aSAndroid Build Coastguard Worker        @Override
132*78c4dd6aSAndroid Build Coastguard Worker        public LocationDetails getLocationDetails()     { return locDetails;}
133*78c4dd6aSAndroid Build Coastguard Worker    }
134*78c4dd6aSAndroid Build Coastguard Worker
135*78c4dd6aSAndroid Build Coastguard Worker    public static class MyIntNode extends IntNode implements LocationProvider
136*78c4dd6aSAndroid Build Coastguard Worker    {
137*78c4dd6aSAndroid Build Coastguard Worker        final LocationDetails locDetails;
138*78c4dd6aSAndroid Build Coastguard Worker
139*78c4dd6aSAndroid Build Coastguard Worker        public MyIntNode(int v, JsonLocation tokenLocation, JsonLocation currentLocation)
140*78c4dd6aSAndroid Build Coastguard Worker        {
141*78c4dd6aSAndroid Build Coastguard Worker            super(v);
142*78c4dd6aSAndroid Build Coastguard Worker            locDetails = new LocationDetailsImpl(tokenLocation, currentLocation);
143*78c4dd6aSAndroid Build Coastguard Worker        }
144*78c4dd6aSAndroid Build Coastguard Worker
145*78c4dd6aSAndroid Build Coastguard Worker        @Override
146*78c4dd6aSAndroid Build Coastguard Worker        public LocationDetails getLocationDetails()     { return locDetails;}
147*78c4dd6aSAndroid Build Coastguard Worker    }
148*78c4dd6aSAndroid Build Coastguard Worker
149*78c4dd6aSAndroid Build Coastguard Worker    public static class MyBooleanNode extends BooleanNode implements LocationProvider
150*78c4dd6aSAndroid Build Coastguard Worker    {
151*78c4dd6aSAndroid Build Coastguard Worker        final LocationDetails locDetails;
152*78c4dd6aSAndroid Build Coastguard Worker
153*78c4dd6aSAndroid Build Coastguard Worker        public MyBooleanNode(boolean v, JsonLocation tokenLocation, JsonLocation currentLocation)
154*78c4dd6aSAndroid Build Coastguard Worker        {
155*78c4dd6aSAndroid Build Coastguard Worker            super(v);
156*78c4dd6aSAndroid Build Coastguard Worker            locDetails = new LocationDetailsImpl(tokenLocation, currentLocation);
157*78c4dd6aSAndroid Build Coastguard Worker        }
158*78c4dd6aSAndroid Build Coastguard Worker
159*78c4dd6aSAndroid Build Coastguard Worker        @Override
160*78c4dd6aSAndroid Build Coastguard Worker        public LocationDetails getLocationDetails()     { return locDetails;}
161*78c4dd6aSAndroid Build Coastguard Worker    }
162*78c4dd6aSAndroid Build Coastguard Worker
163*78c4dd6aSAndroid Build Coastguard Worker    public static class MyArrayNode extends ArrayNode implements LocationProvider
164*78c4dd6aSAndroid Build Coastguard Worker    {
165*78c4dd6aSAndroid Build Coastguard Worker        final LocationDetails locDetails;
166*78c4dd6aSAndroid Build Coastguard Worker
167*78c4dd6aSAndroid Build Coastguard Worker        public MyArrayNode(JsonNodeFactory nc, JsonLocation tokenLocation, JsonLocation currentLocation)
168*78c4dd6aSAndroid Build Coastguard Worker        {
169*78c4dd6aSAndroid Build Coastguard Worker            super(nc);
170*78c4dd6aSAndroid Build Coastguard Worker            locDetails = new LocationDetailsImpl(tokenLocation, currentLocation);
171*78c4dd6aSAndroid Build Coastguard Worker        }
172*78c4dd6aSAndroid Build Coastguard Worker
173*78c4dd6aSAndroid Build Coastguard Worker        @Override
174*78c4dd6aSAndroid Build Coastguard Worker        public LocationDetails getLocationDetails()     { return locDetails;}
175*78c4dd6aSAndroid Build Coastguard Worker    }
176*78c4dd6aSAndroid Build Coastguard Worker
177*78c4dd6aSAndroid Build Coastguard Worker    public static class MyObjectNode extends ObjectNode implements LocationProvider
178*78c4dd6aSAndroid Build Coastguard Worker    {
179*78c4dd6aSAndroid Build Coastguard Worker        final LocationDetails locDetails;
180*78c4dd6aSAndroid Build Coastguard Worker
181*78c4dd6aSAndroid Build Coastguard Worker        public MyObjectNode(JsonNodeFactory nc, JsonLocation tokenLocation, JsonLocation currentLocation)
182*78c4dd6aSAndroid Build Coastguard Worker        {
183*78c4dd6aSAndroid Build Coastguard Worker            super(nc);
184*78c4dd6aSAndroid Build Coastguard Worker            locDetails = new LocationDetailsImpl(tokenLocation, currentLocation);
185*78c4dd6aSAndroid Build Coastguard Worker        }
186*78c4dd6aSAndroid Build Coastguard Worker
187*78c4dd6aSAndroid Build Coastguard Worker        @Override
188*78c4dd6aSAndroid Build Coastguard Worker        public LocationDetails getLocationDetails()     { return locDetails;}
189*78c4dd6aSAndroid Build Coastguard Worker    }
190*78c4dd6aSAndroid Build Coastguard Worker```
191*78c4dd6aSAndroid Build Coastguard Worker
192*78c4dd6aSAndroid Build Coastguard Worker### Scenario 1, solution part 3 - using the custom `JsonNodeFactory`
193*78c4dd6aSAndroid Build Coastguard Worker
194*78c4dd6aSAndroid Build Coastguard WorkerWith the pieces we now have, we just need to tell the YAML library to make of use them, which involves a minor and simple modification to the normal sequence of processing.
195*78c4dd6aSAndroid Build Coastguard Worker
196*78c4dd6aSAndroid Build Coastguard Worker```java
197*78c4dd6aSAndroid Build Coastguard Worker    this.yamlFactory = new YAMLFactory();
198*78c4dd6aSAndroid Build Coastguard Worker
199*78c4dd6aSAndroid Build Coastguard Worker    try (YAMLParser yp = yamlFactory.createParser(f);)
200*78c4dd6aSAndroid Build Coastguard Worker    {
201*78c4dd6aSAndroid Build Coastguard Worker        ObjectReader rdr = mapper.reader(new MyNodeFactory(yp));
202*78c4dd6aSAndroid Build Coastguard Worker        JsonNode jsonNode = rdr.readTree(yp);
203*78c4dd6aSAndroid Build Coastguard Worker        Set<ValidationMessage> msgs = mySchema.validate(jsonNode);
204*78c4dd6aSAndroid Build Coastguard Worker
205*78c4dd6aSAndroid Build Coastguard Worker        if (msgs.isEmpty())
206*78c4dd6aSAndroid Build Coastguard Worker        {
207*78c4dd6aSAndroid Build Coastguard Worker            for (JsonNode item : jsonNode.get("someItem"))
208*78c4dd6aSAndroid Build Coastguard Worker            {
209*78c4dd6aSAndroid Build Coastguard Worker                processJsonItems(item);
210*78c4dd6aSAndroid Build Coastguard Worker            }
211*78c4dd6aSAndroid Build Coastguard Worker        }
212*78c4dd6aSAndroid Build Coastguard Worker        else
213*78c4dd6aSAndroid Build Coastguard Worker        {
214*78c4dd6aSAndroid Build Coastguard Worker            //  ... we'll look at how to get line locations for ValidationMessage cases in Scenario 2
215*78c4dd6aSAndroid Build Coastguard Worker        }
216*78c4dd6aSAndroid Build Coastguard Worker
217*78c4dd6aSAndroid Build Coastguard Worker    }
218*78c4dd6aSAndroid Build Coastguard Worker    // a JsonProcessingException seems to be the base exception for "gross" errors e.g.
219*78c4dd6aSAndroid Build Coastguard Worker    // missing quotes at end of string etc.
220*78c4dd6aSAndroid Build Coastguard Worker    catch (JsonProcessingException jpEx)
221*78c4dd6aSAndroid Build Coastguard Worker    {
222*78c4dd6aSAndroid Build Coastguard Worker        JsonLocation loc = jpEx.getLocation();
223*78c4dd6aSAndroid Build Coastguard Worker        // ... do something with the loc details
224*78c4dd6aSAndroid Build Coastguard Worker    }
225*78c4dd6aSAndroid Build Coastguard Worker```
226*78c4dd6aSAndroid Build Coastguard WorkerSome notes on what is happening here:
227*78c4dd6aSAndroid Build Coastguard Worker
228*78c4dd6aSAndroid Build Coastguard Worker* We instantiate our custom JsonNodeFactory with the YAMLParser reference, and the line locations get recorded for us as the file is parsed.
229*78c4dd6aSAndroid Build Coastguard Worker* If any exceptions are thrown, they will already contain a JsonLocation object that we can use directly if needed
230*78c4dd6aSAndroid Build Coastguard Worker* If we get no validation messages, we know the JSON tree matches the schema and we can do any post processing we need on the tree. We'll see how to report any issues with this in the next part
231*78c4dd6aSAndroid Build Coastguard Worker* We'll look at how to get line locations for ValidationMessage errors in Scenario 2
232*78c4dd6aSAndroid Build Coastguard Worker
233*78c4dd6aSAndroid Build Coastguard Worker### Scenario 1, solution part 4 - extracting the line details
234*78c4dd6aSAndroid Build Coastguard Worker
235*78c4dd6aSAndroid Build Coastguard WorkerHaving got everything prepared, actually getting the line locations is rather easy
236*78c4dd6aSAndroid Build Coastguard Worker
237*78c4dd6aSAndroid Build Coastguard Worker
238*78c4dd6aSAndroid Build Coastguard Worker```java
239*78c4dd6aSAndroid Build Coastguard Worker    void processJsonItems(JsonNode item)
240*78c4dd6aSAndroid Build Coastguard Worker    {
241*78c4dd6aSAndroid Build Coastguard Worker        Iterator<Map.Entry<String, JsonNode>> iter = item.fields();
242*78c4dd6aSAndroid Build Coastguard Worker
243*78c4dd6aSAndroid Build Coastguard Worker        while (iter.hasNext())
244*78c4dd6aSAndroid Build Coastguard Worker        {
245*78c4dd6aSAndroid Build Coastguard Worker            Map.Entry<String, JsonNode> node = iter.next();
246*78c4dd6aSAndroid Build Coastguard Worker            extractErrorLocation(node.getValue());
247*78c4dd6aSAndroid Build Coastguard Worker        }
248*78c4dd6aSAndroid Build Coastguard Worker    }
249*78c4dd6aSAndroid Build Coastguard Worker
250*78c4dd6aSAndroid Build Coastguard Worker    void extractErrorLocation(JsonNode node)
251*78c4dd6aSAndroid Build Coastguard Worker    {
252*78c4dd6aSAndroid Build Coastguard Worker        if (node == null || !(node instanceof LocationProvider))    { return; }
253*78c4dd6aSAndroid Build Coastguard Worker
254*78c4dd6aSAndroid Build Coastguard Worker        //Note: we also know the "span" of the error section i.e. from token location to current location (first char after the token)
255*78c4dd6aSAndroid Build Coastguard Worker        //      if we wanted at some stage we could use this to highlight/underline all of the text in error
256*78c4dd6aSAndroid Build Coastguard Worker        LocationDetails dets = ((LocationProvider) node).getLocationDetails();
257*78c4dd6aSAndroid Build Coastguard Worker        // ... do something with the details e.g. report an error/issue against the YAML line
258*78c4dd6aSAndroid Build Coastguard Worker    }
259*78c4dd6aSAndroid Build Coastguard Worker```
260*78c4dd6aSAndroid Build Coastguard Worker
261*78c4dd6aSAndroid Build Coastguard WorkerSo that's pretty much it - as we are processing the JSON tree, if there is any point we want to report something about the contents, we can do so with a reference back to the original YAML line number.
262*78c4dd6aSAndroid Build Coastguard Worker
263*78c4dd6aSAndroid Build Coastguard WorkerThere is still a problem though, what if the validation against the schema fails?
264*78c4dd6aSAndroid Build Coastguard Worker
265*78c4dd6aSAndroid Build Coastguard Worker## Scenario 2 - ValidationMessage line locations
266*78c4dd6aSAndroid Build Coastguard Worker
267*78c4dd6aSAndroid Build Coastguard WorkerAny failures validation against the schema come back in the form of a set of `ValidationMessage` objects. But these also do not contain original YAML source line information, and there's no easy way to inject it as we did for Scenario 1. Luckily though, there is a trick we can use here!
268*78c4dd6aSAndroid Build Coastguard Worker
269*78c4dd6aSAndroid Build Coastguard WorkerWithin the `ValidationMessage` object is something called the 'path' of the error, which we can access with the `getPath()` method. The syntax of this path by default is close to being [JSONPath](https://datatracker.ietf.org/doc/draft-ietf-jsonpath-base/), but can be set explicitly to be
270*78c4dd6aSAndroid Build Coastguard Workereither [JSONPath](https://datatracker.ietf.org/doc/draft-ietf-jsonpath-base/) or [JSONPointer](https://www.rfc-editor.org/rfc/rfc6901.html) expressions. In our case as we already use [Jackson](https://github.com/FasterXML/jackson) which supports node lookups based on JSONPointer expressions,
271*78c4dd6aSAndroid Build Coastguard Workerwe will set the path expressions to be JSONPointers. This is achieved by configuring the reported path type through the `SchemaValidatorsConfig` before we read our schema:
272*78c4dd6aSAndroid Build Coastguard Worker
273*78c4dd6aSAndroid Build Coastguard Worker```java
274*78c4dd6aSAndroid Build Coastguard Worker    SchemaValidatorsConfig config = new SchemaValidatorsConfig();
275*78c4dd6aSAndroid Build Coastguard Worker    config.setPathType(PathType.JSON_POINTER);
276*78c4dd6aSAndroid Build Coastguard Worker    JsonSchema jsonSchema = JsonSchemaFactory.getInstance().getSchema(schema, config);
277*78c4dd6aSAndroid Build Coastguard Worker```
278*78c4dd6aSAndroid Build Coastguard Worker
279*78c4dd6aSAndroid Build Coastguard WorkerHaving set paths to be JSONPointer expressions we can use those pointers for locating the appropriate `JsonNode` instances. The following couple of methods illustrate this process:
280*78c4dd6aSAndroid Build Coastguard Worker
281*78c4dd6aSAndroid Build Coastguard Worker```java
282*78c4dd6aSAndroid Build Coastguard Worker    JsonNode findJsonNode(ValidationMessage msg, JsonNode rootNode)
283*78c4dd6aSAndroid Build Coastguard Worker    {
284*78c4dd6aSAndroid Build Coastguard Worker        // Construct the JSONPointer.
285*78c4dd6aSAndroid Build Coastguard Worker        JsonPointer pathPtr = JsonPointer.valueOf(msg.getPath());
286*78c4dd6aSAndroid Build Coastguard Worker        // Now see if we can find the node.
287*78c4dd6aSAndroid Build Coastguard Worker        JsonNode node = rootNode.at(pathPtr);
288*78c4dd6aSAndroid Build Coastguard Worker        return node;
289*78c4dd6aSAndroid Build Coastguard Worker    }
290*78c4dd6aSAndroid Build Coastguard Worker
291*78c4dd6aSAndroid Build Coastguard Worker    LocationDetails getLocationDetails(ValidationMessage msg, JsonNode rootNode)
292*78c4dd6aSAndroid Build Coastguard Worker    {
293*78c4dd6aSAndroid Build Coastguard Worker        LocationDetails retval = null;
294*78c4dd6aSAndroid Build Coastguard Worker        JsonNode node = findJsonNode(msg, rootNode);
295*78c4dd6aSAndroid Build Coastguard Worker        if (node != null && node instanceof LocationProvider)
296*78c4dd6aSAndroid Build Coastguard Worker        {
297*78c4dd6aSAndroid Build Coastguard Worker            retval = ((LocationProvider) node).getLocationDetails();
298*78c4dd6aSAndroid Build Coastguard Worker        }
299*78c4dd6aSAndroid Build Coastguard Worker        return retval;
300*78c4dd6aSAndroid Build Coastguard Worker    }
301*78c4dd6aSAndroid Build Coastguard Worker```
302*78c4dd6aSAndroid Build Coastguard Worker
303*78c4dd6aSAndroid Build Coastguard Worker## Summary
304*78c4dd6aSAndroid Build Coastguard Worker
305*78c4dd6aSAndroid Build Coastguard WorkerAlthough not trivial, the steps outlined here give us a way to track back to the original source YAML for a variety of possible reporting cases:
306*78c4dd6aSAndroid Build Coastguard Worker
307*78c4dd6aSAndroid Build Coastguard Worker* JSON processing exceptions (mostly already done for us)
308*78c4dd6aSAndroid Build Coastguard Worker* Issues flagged during validation of the YAML against the schema
309*78c4dd6aSAndroid Build Coastguard Worker* Anything we need to report with source information during post processing of the validated JSON tree
310