xref: /aosp_15_r20/external/json-schema-validator/doc/schema-retrieval.md (revision 78c4dd6aa35290980cdcd1623a7e337e8d021c7c)
1# Customizing Schema Retrieval
2
3A schema can be identified by its schema identifier which is indicated using the `$id` keyword or `id` keyword in earlier drafts. This is an absolute IRI that uniquely identifies the schema and is not necessarily a network locator. A schema need not be downloadable from it's absolute IRI.
4
5In the event a schema references a schema identifier that is not a subschema resource, for instance defined in the `$defs` keyword or `definitions` keyword. The library will need to be able to retrieve the schema given its schema identifier.
6
7In the event that the schema does not define a schema identifier using the `$id` keyword, the retrieval IRI will be used as it's schema identifier.
8
9## Loading Schemas from memory
10
11Schemas can be loaded through a map.
12
13```java
14String schemaData = "{\r\n"
15        + "  \"type\": \"integer\"\r\n"
16        + "}";
17Map<String, String> schemas = Collections.singletonMap("https://www.example.com/integer.json", schemaData);
18JsonSchemaFactory schemaFactory = JsonSchemaFactory
19    .getInstance(VersionFlag.V7,
20        builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(schemas)));
21```
22
23Schemas can be loaded through a function.
24
25```java
26String schemaData = "{\r\n"
27        + "  \"type\": \"integer\"\r\n"
28        + "}";
29Map<String, String> schemas = Collections.singletonMap("https://www.example.com/integer.json", schemaData);
30    JsonSchemaFactory schemaFactory = JsonSchemaFactory
31        .getInstance(VersionFlag.V7,
32            builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(schemas::get)));
33```
34
35Schemas can also be loaded in the following manner.
36
37```java
38class RegistryEntry {
39    private final String schemaData;
40
41    public RegistryEntry(String schemaData) {
42        this.schemaData = schemaData;
43    }
44
45    public String getSchemaData() {
46        return this.schemaData;
47    }
48}
49
50String schemaData = "{\r\n"
51        + "  \"type\": \"integer\"\r\n"
52        + "}";
53Map<String, RegistryEntry> registry = Collections
54    .singletonMap("https://www.example.com/integer.json", new RegistryEntry(schemaData));
55JsonSchemaFactory schemaFactory = JsonSchemaFactory
56    .getInstance(VersionFlag.V7, builder -> builder
57        .schemaLoaders(schemaLoaders -> schemaLoaders.schemas(registry::get, RegistryEntry::getSchemaData)));
58```
59
60## Mapping Schema Identifier to Retrieval IRI
61
62The schema identifier can be mapped to the retrieval IRI by implementing the `SchemaMapper` interface.
63
64### Configuring Schema Mapper
65
66```java
67class CustomSchemaMapper implements SchemaMapper {
68    @Override
69    public AbsoluteIri map(AbsoluteIri absoluteIRI) {
70        String iri = absoluteIRI.toString();
71        if ("https://www.example.com/integer.json".equals(iri)) {
72            return AbsoluteIri.of("classpath:schemas/integer.json");
73        }
74        return null;
75    }
76}
77
78JsonSchemaFactory schemaFactory = JsonSchemaFactory
79    .getInstance(VersionFlag.V7,
80        builder -> builder.schemaMappers(schemaMappers -> schemaMappers.add(new CustomSchemaMapper())));
81```
82
83### Configuring Prefix Mappings
84
85```java
86JsonSchemaFactory schemaFactory = JsonSchemaFactory
87    .getInstance(VersionFlag.V7,
88        builder -> builder
89            .schemaMappers(schemaMappers -> schemaMappers
90                .mapPrefix("https://json-schema.org", "classpath:")
91                .mapPrefix("http://json-schema.org", "classpath:")));
92```
93
94### Configuring Mappings
95
96```java
97Map<String, String> mappings = Collections
98    .singletonMap("https://www.example.com/integer.json", "classpath:schemas/integer.json");
99
100JsonSchemaFactory schemaFactory = JsonSchemaFactory
101    .getInstance(VersionFlag.V7,
102        builder -> builder.schemaMappers(schemaMappers -> schemaMappers.mappings(mappings)));
103```
104
105## Customizing Network Schema Retrieval
106
107The default `UriSchemaLoader` implementation uses JDK connection/socket without handling network exceptions. It works in most of the cases; however, if you want to have a customized implementation, you can do so. One user has his implementation with urirest to handle the timeout. A detailed discussion can be found in this [issue](https://github.com/networknt/json-schema-validator/issues/240)
108
109### Configuring Custom URI Schema Loader
110
111The default `UriSchemaLoader` can be overwritten in order to customize its behaviour in regards of authorization or error handling.
112
113The `SchemaLoader` interface must implemented and the implementation configured on the `JsonSchemaFactory`.
114
115```java
116public class CustomUriSchemaLoader implements SchemaLoader {
117    private static final Logger LOGGER = LoggerFactory.getLogger(CustomUriSchemaLoader.class);
118    private final String        authorizationToken;
119    private final HttpClient    client;
120
121    public CustomUriSchemaLoader(String authorizationToken) {
122        this.authorizationToken = authorizationToken;
123        this.client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
124    }
125
126    @Override
127    public InputStreamSource getSchema(AbsoluteIri absoluteIri) {
128        String scheme = absoluteIri.getScheme();
129        if ("https".equals(scheme) || "http".equals(scheme)) {
130            URI uri = URI.create(absoluteIri.toString());
131            return () -> {
132                HttpRequest request = HttpRequest.newBuilder().uri(uri).header("Authorization", authorizationToken).build();
133                try {
134                    HttpResponse<String> response = this.client.send(request, HttpResponse.BodyHandlers.ofString());
135                    if ((200 > response.statusCode()) || (response.statusCode() > 299)) {
136                        String errorMessage = String.format("Could not get data from schema endpoint. The following status %d was returned.", response.statusCode());
137                        LOGGER.error(errorMessage);
138                    }
139                    return new ByteArrayInputStream(response.body().getBytes(StandardCharsets.UTF_8));
140                } catch (InterruptedException e) {
141                    throw new RuntimeException(e);
142                }
143            }
144        }
145        return null;
146    }
147}
148```
149
150Within the `JsonSchemaFactory` the custom `SchemaLoader` must be configured.
151
152```java
153CustomUriSchemaLoader uriSchemaLoader = new CustomUriSchemaLoader(authorizationToken);
154
155JsonSchemaFactory schemaFactory = JsonSchemaFactory
156    .getInstance(VersionFlag.V7,
157        builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.add(uriSchemaLoader)));
158```
159