xref: /aosp_15_r20/external/json-schema-validator/doc/schema-retrieval.md (revision 78c4dd6aa35290980cdcd1623a7e337e8d021c7c)
1*78c4dd6aSAndroid Build Coastguard Worker# Customizing Schema Retrieval
2*78c4dd6aSAndroid Build Coastguard Worker
3*78c4dd6aSAndroid Build Coastguard WorkerA 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*78c4dd6aSAndroid Build Coastguard Worker
5*78c4dd6aSAndroid Build Coastguard WorkerIn 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*78c4dd6aSAndroid Build Coastguard Worker
7*78c4dd6aSAndroid Build Coastguard WorkerIn 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*78c4dd6aSAndroid Build Coastguard Worker
9*78c4dd6aSAndroid Build Coastguard Worker## Loading Schemas from memory
10*78c4dd6aSAndroid Build Coastguard Worker
11*78c4dd6aSAndroid Build Coastguard WorkerSchemas can be loaded through a map.
12*78c4dd6aSAndroid Build Coastguard Worker
13*78c4dd6aSAndroid Build Coastguard Worker```java
14*78c4dd6aSAndroid Build Coastguard WorkerString schemaData = "{\r\n"
15*78c4dd6aSAndroid Build Coastguard Worker        + "  \"type\": \"integer\"\r\n"
16*78c4dd6aSAndroid Build Coastguard Worker        + "}";
17*78c4dd6aSAndroid Build Coastguard WorkerMap<String, String> schemas = Collections.singletonMap("https://www.example.com/integer.json", schemaData);
18*78c4dd6aSAndroid Build Coastguard WorkerJsonSchemaFactory schemaFactory = JsonSchemaFactory
19*78c4dd6aSAndroid Build Coastguard Worker    .getInstance(VersionFlag.V7,
20*78c4dd6aSAndroid Build Coastguard Worker        builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(schemas)));
21*78c4dd6aSAndroid Build Coastguard Worker```
22*78c4dd6aSAndroid Build Coastguard Worker
23*78c4dd6aSAndroid Build Coastguard WorkerSchemas can be loaded through a function.
24*78c4dd6aSAndroid Build Coastguard Worker
25*78c4dd6aSAndroid Build Coastguard Worker```java
26*78c4dd6aSAndroid Build Coastguard WorkerString schemaData = "{\r\n"
27*78c4dd6aSAndroid Build Coastguard Worker        + "  \"type\": \"integer\"\r\n"
28*78c4dd6aSAndroid Build Coastguard Worker        + "}";
29*78c4dd6aSAndroid Build Coastguard WorkerMap<String, String> schemas = Collections.singletonMap("https://www.example.com/integer.json", schemaData);
30*78c4dd6aSAndroid Build Coastguard Worker    JsonSchemaFactory schemaFactory = JsonSchemaFactory
31*78c4dd6aSAndroid Build Coastguard Worker        .getInstance(VersionFlag.V7,
32*78c4dd6aSAndroid Build Coastguard Worker            builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(schemas::get)));
33*78c4dd6aSAndroid Build Coastguard Worker```
34*78c4dd6aSAndroid Build Coastguard Worker
35*78c4dd6aSAndroid Build Coastguard WorkerSchemas can also be loaded in the following manner.
36*78c4dd6aSAndroid Build Coastguard Worker
37*78c4dd6aSAndroid Build Coastguard Worker```java
38*78c4dd6aSAndroid Build Coastguard Workerclass RegistryEntry {
39*78c4dd6aSAndroid Build Coastguard Worker    private final String schemaData;
40*78c4dd6aSAndroid Build Coastguard Worker
41*78c4dd6aSAndroid Build Coastguard Worker    public RegistryEntry(String schemaData) {
42*78c4dd6aSAndroid Build Coastguard Worker        this.schemaData = schemaData;
43*78c4dd6aSAndroid Build Coastguard Worker    }
44*78c4dd6aSAndroid Build Coastguard Worker
45*78c4dd6aSAndroid Build Coastguard Worker    public String getSchemaData() {
46*78c4dd6aSAndroid Build Coastguard Worker        return this.schemaData;
47*78c4dd6aSAndroid Build Coastguard Worker    }
48*78c4dd6aSAndroid Build Coastguard Worker}
49*78c4dd6aSAndroid Build Coastguard Worker
50*78c4dd6aSAndroid Build Coastguard WorkerString schemaData = "{\r\n"
51*78c4dd6aSAndroid Build Coastguard Worker        + "  \"type\": \"integer\"\r\n"
52*78c4dd6aSAndroid Build Coastguard Worker        + "}";
53*78c4dd6aSAndroid Build Coastguard WorkerMap<String, RegistryEntry> registry = Collections
54*78c4dd6aSAndroid Build Coastguard Worker    .singletonMap("https://www.example.com/integer.json", new RegistryEntry(schemaData));
55*78c4dd6aSAndroid Build Coastguard WorkerJsonSchemaFactory schemaFactory = JsonSchemaFactory
56*78c4dd6aSAndroid Build Coastguard Worker    .getInstance(VersionFlag.V7, builder -> builder
57*78c4dd6aSAndroid Build Coastguard Worker        .schemaLoaders(schemaLoaders -> schemaLoaders.schemas(registry::get, RegistryEntry::getSchemaData)));
58*78c4dd6aSAndroid Build Coastguard Worker```
59*78c4dd6aSAndroid Build Coastguard Worker
60*78c4dd6aSAndroid Build Coastguard Worker## Mapping Schema Identifier to Retrieval IRI
61*78c4dd6aSAndroid Build Coastguard Worker
62*78c4dd6aSAndroid Build Coastguard WorkerThe schema identifier can be mapped to the retrieval IRI by implementing the `SchemaMapper` interface.
63*78c4dd6aSAndroid Build Coastguard Worker
64*78c4dd6aSAndroid Build Coastguard Worker### Configuring Schema Mapper
65*78c4dd6aSAndroid Build Coastguard Worker
66*78c4dd6aSAndroid Build Coastguard Worker```java
67*78c4dd6aSAndroid Build Coastguard Workerclass CustomSchemaMapper implements SchemaMapper {
68*78c4dd6aSAndroid Build Coastguard Worker    @Override
69*78c4dd6aSAndroid Build Coastguard Worker    public AbsoluteIri map(AbsoluteIri absoluteIRI) {
70*78c4dd6aSAndroid Build Coastguard Worker        String iri = absoluteIRI.toString();
71*78c4dd6aSAndroid Build Coastguard Worker        if ("https://www.example.com/integer.json".equals(iri)) {
72*78c4dd6aSAndroid Build Coastguard Worker            return AbsoluteIri.of("classpath:schemas/integer.json");
73*78c4dd6aSAndroid Build Coastguard Worker        }
74*78c4dd6aSAndroid Build Coastguard Worker        return null;
75*78c4dd6aSAndroid Build Coastguard Worker    }
76*78c4dd6aSAndroid Build Coastguard Worker}
77*78c4dd6aSAndroid Build Coastguard Worker
78*78c4dd6aSAndroid Build Coastguard WorkerJsonSchemaFactory schemaFactory = JsonSchemaFactory
79*78c4dd6aSAndroid Build Coastguard Worker    .getInstance(VersionFlag.V7,
80*78c4dd6aSAndroid Build Coastguard Worker        builder -> builder.schemaMappers(schemaMappers -> schemaMappers.add(new CustomSchemaMapper())));
81*78c4dd6aSAndroid Build Coastguard Worker```
82*78c4dd6aSAndroid Build Coastguard Worker
83*78c4dd6aSAndroid Build Coastguard Worker### Configuring Prefix Mappings
84*78c4dd6aSAndroid Build Coastguard Worker
85*78c4dd6aSAndroid Build Coastguard Worker```java
86*78c4dd6aSAndroid Build Coastguard WorkerJsonSchemaFactory schemaFactory = JsonSchemaFactory
87*78c4dd6aSAndroid Build Coastguard Worker    .getInstance(VersionFlag.V7,
88*78c4dd6aSAndroid Build Coastguard Worker        builder -> builder
89*78c4dd6aSAndroid Build Coastguard Worker            .schemaMappers(schemaMappers -> schemaMappers
90*78c4dd6aSAndroid Build Coastguard Worker                .mapPrefix("https://json-schema.org", "classpath:")
91*78c4dd6aSAndroid Build Coastguard Worker                .mapPrefix("http://json-schema.org", "classpath:")));
92*78c4dd6aSAndroid Build Coastguard Worker```
93*78c4dd6aSAndroid Build Coastguard Worker
94*78c4dd6aSAndroid Build Coastguard Worker### Configuring Mappings
95*78c4dd6aSAndroid Build Coastguard Worker
96*78c4dd6aSAndroid Build Coastguard Worker```java
97*78c4dd6aSAndroid Build Coastguard WorkerMap<String, String> mappings = Collections
98*78c4dd6aSAndroid Build Coastguard Worker    .singletonMap("https://www.example.com/integer.json", "classpath:schemas/integer.json");
99*78c4dd6aSAndroid Build Coastguard Worker
100*78c4dd6aSAndroid Build Coastguard WorkerJsonSchemaFactory schemaFactory = JsonSchemaFactory
101*78c4dd6aSAndroid Build Coastguard Worker    .getInstance(VersionFlag.V7,
102*78c4dd6aSAndroid Build Coastguard Worker        builder -> builder.schemaMappers(schemaMappers -> schemaMappers.mappings(mappings)));
103*78c4dd6aSAndroid Build Coastguard Worker```
104*78c4dd6aSAndroid Build Coastguard Worker
105*78c4dd6aSAndroid Build Coastguard Worker## Customizing Network Schema Retrieval
106*78c4dd6aSAndroid Build Coastguard Worker
107*78c4dd6aSAndroid Build Coastguard WorkerThe 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*78c4dd6aSAndroid Build Coastguard Worker
109*78c4dd6aSAndroid Build Coastguard Worker### Configuring Custom URI Schema Loader
110*78c4dd6aSAndroid Build Coastguard Worker
111*78c4dd6aSAndroid Build Coastguard WorkerThe default `UriSchemaLoader` can be overwritten in order to customize its behaviour in regards of authorization or error handling.
112*78c4dd6aSAndroid Build Coastguard Worker
113*78c4dd6aSAndroid Build Coastguard WorkerThe `SchemaLoader` interface must implemented and the implementation configured on the `JsonSchemaFactory`.
114*78c4dd6aSAndroid Build Coastguard Worker
115*78c4dd6aSAndroid Build Coastguard Worker```java
116*78c4dd6aSAndroid Build Coastguard Workerpublic class CustomUriSchemaLoader implements SchemaLoader {
117*78c4dd6aSAndroid Build Coastguard Worker    private static final Logger LOGGER = LoggerFactory.getLogger(CustomUriSchemaLoader.class);
118*78c4dd6aSAndroid Build Coastguard Worker    private final String        authorizationToken;
119*78c4dd6aSAndroid Build Coastguard Worker    private final HttpClient    client;
120*78c4dd6aSAndroid Build Coastguard Worker
121*78c4dd6aSAndroid Build Coastguard Worker    public CustomUriSchemaLoader(String authorizationToken) {
122*78c4dd6aSAndroid Build Coastguard Worker        this.authorizationToken = authorizationToken;
123*78c4dd6aSAndroid Build Coastguard Worker        this.client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
124*78c4dd6aSAndroid Build Coastguard Worker    }
125*78c4dd6aSAndroid Build Coastguard Worker
126*78c4dd6aSAndroid Build Coastguard Worker    @Override
127*78c4dd6aSAndroid Build Coastguard Worker    public InputStreamSource getSchema(AbsoluteIri absoluteIri) {
128*78c4dd6aSAndroid Build Coastguard Worker        String scheme = absoluteIri.getScheme();
129*78c4dd6aSAndroid Build Coastguard Worker        if ("https".equals(scheme) || "http".equals(scheme)) {
130*78c4dd6aSAndroid Build Coastguard Worker            URI uri = URI.create(absoluteIri.toString());
131*78c4dd6aSAndroid Build Coastguard Worker            return () -> {
132*78c4dd6aSAndroid Build Coastguard Worker                HttpRequest request = HttpRequest.newBuilder().uri(uri).header("Authorization", authorizationToken).build();
133*78c4dd6aSAndroid Build Coastguard Worker                try {
134*78c4dd6aSAndroid Build Coastguard Worker                    HttpResponse<String> response = this.client.send(request, HttpResponse.BodyHandlers.ofString());
135*78c4dd6aSAndroid Build Coastguard Worker                    if ((200 > response.statusCode()) || (response.statusCode() > 299)) {
136*78c4dd6aSAndroid Build Coastguard Worker                        String errorMessage = String.format("Could not get data from schema endpoint. The following status %d was returned.", response.statusCode());
137*78c4dd6aSAndroid Build Coastguard Worker                        LOGGER.error(errorMessage);
138*78c4dd6aSAndroid Build Coastguard Worker                    }
139*78c4dd6aSAndroid Build Coastguard Worker                    return new ByteArrayInputStream(response.body().getBytes(StandardCharsets.UTF_8));
140*78c4dd6aSAndroid Build Coastguard Worker                } catch (InterruptedException e) {
141*78c4dd6aSAndroid Build Coastguard Worker                    throw new RuntimeException(e);
142*78c4dd6aSAndroid Build Coastguard Worker                }
143*78c4dd6aSAndroid Build Coastguard Worker            }
144*78c4dd6aSAndroid Build Coastguard Worker        }
145*78c4dd6aSAndroid Build Coastguard Worker        return null;
146*78c4dd6aSAndroid Build Coastguard Worker    }
147*78c4dd6aSAndroid Build Coastguard Worker}
148*78c4dd6aSAndroid Build Coastguard Worker```
149*78c4dd6aSAndroid Build Coastguard Worker
150*78c4dd6aSAndroid Build Coastguard WorkerWithin the `JsonSchemaFactory` the custom `SchemaLoader` must be configured.
151*78c4dd6aSAndroid Build Coastguard Worker
152*78c4dd6aSAndroid Build Coastguard Worker```java
153*78c4dd6aSAndroid Build Coastguard WorkerCustomUriSchemaLoader uriSchemaLoader = new CustomUriSchemaLoader(authorizationToken);
154*78c4dd6aSAndroid Build Coastguard Worker
155*78c4dd6aSAndroid Build Coastguard WorkerJsonSchemaFactory schemaFactory = JsonSchemaFactory
156*78c4dd6aSAndroid Build Coastguard Worker    .getInstance(VersionFlag.V7,
157*78c4dd6aSAndroid Build Coastguard Worker        builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.add(uriSchemaLoader)));
158*78c4dd6aSAndroid Build Coastguard Worker```
159