Skip to content

Commit 27f9da8

Browse files
committed
Align OGC API - Processes ruleset with final v2.0 spec (18-062r3)
The Processes rulesets were based on an earlier draft. Reconcile the four covered conformance classes with the final v2.0 spec: - Bump conformance-class and requirement URIs from 1.0 to 2.0 (core, job-list, json, ogc-process-description). - Fix requirement identifiers to match the spec (slash -> hyphen for process-exception-no-such-process and the job-results-exception-* rules) and correct the underscore doc-link anchors. - Resolve the ogc-process-description v2 schema TODO (point process.yaml at the master branch, which tracks 2.0; the schemas.opengis.net 2.0 tree is not published yet). - Drop the execution POST from the JSON media-type rule; v2 no longer lists it (execution output is content-negotiated). - Add the two new statically-checkable requirements: job-result-op-0th (path /jobs/{jobID}/results/{outputID}/0) and job-results-exception-no-such-output. - Add type: object to the example results schema so it validates against the v2 results.yaml, and add the /0 path to the example. Update and extend tests accordingly.
1 parent 72e4864 commit 27f9da8

7 files changed

Lines changed: 128 additions & 34 deletions

File tree

src/specs/ogc-api/examples/processes.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,32 @@
250250
}
251251
}
252252
}
253+
},
254+
"/jobs/{jobID}/results/{outputID}/0": {
255+
"get": {
256+
"summary": "retrieve the first value of a single result of a job",
257+
"description": "Retrieves the first (0th) value of a single output of a job.",
258+
"operationId": "getResultOutputValue",
259+
"parameters": [
260+
{
261+
"$ref": "#/components/parameters/jobID"
262+
},
263+
{
264+
"$ref": "#/components/parameters/outputID"
265+
}
266+
],
267+
"responses": {
268+
"200": {
269+
"$ref": "#/components/responses/JobResult"
270+
},
271+
"404": {
272+
"$ref": "#/components/responses/NotFound"
273+
},
274+
"500": {
275+
"$ref": "#/components/responses/ServerError"
276+
}
277+
}
278+
}
253279
}
254280
},
255281
"components": {
@@ -1049,6 +1075,7 @@
10491075
"additionalProperties": false
10501076
},
10511077
"results": {
1078+
"type": "object",
10521079
"additionalProperties": {
10531080
"$ref": "#/components/schemas/inlineOrRefData"
10541081
}

src/specs/ogc-api/rulesets/processes-core.test.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -169,13 +169,13 @@ describe('/req/core/process-description-success', () => {
169169
});
170170
});
171171

172-
describe('/req/core/process-exception/no-such-process', () => {
172+
describe('/req/core/process-exception-no-such-process', () => {
173173
test('Fails when process description 404 response is absent', async () => {
174174
const oasDoc = clone(exampleDoc);
175175
delete (oasDoc.paths['/processes/{processID}'].get.responses as Record<string, unknown>)['404'];
176176
const violations = await spectral.run(oasDoc);
177177

178-
expect(violations).toContainViolation('/req/core/process-exception/no-such-process', 1);
178+
expect(violations).toContainViolation('/req/core/process-exception-no-such-process', 1);
179179
});
180180

181181
test('Fails when process description 404 response schema is invalid', async () => {
@@ -192,7 +192,7 @@ describe('/req/core/process-exception/no-such-process', () => {
192192

193193
const violations = await spectral.run(oasDoc);
194194

195-
expect(violations).toContainViolation('/req/core/process-exception/no-such-process', 1);
195+
expect(violations).toContainViolation('/req/core/process-exception-no-such-process', 1);
196196
});
197197
});
198198

@@ -389,6 +389,24 @@ describe('/req/core/job-result-op', () => {
389389
});
390390
});
391391

392+
describe('/req/core/job-result-op-0th', () => {
393+
test('Fails when the 0th job result path is absent', async () => {
394+
const oasDoc = clone(exampleDoc);
395+
delete (oasDoc.paths as Record<string, unknown>)['/jobs/{jobID}/results/{outputID}/0'];
396+
const violations = await spectral.run(oasDoc);
397+
398+
expect(violations).toContainViolation('/req/core/job-result-op-0th', 1);
399+
});
400+
401+
test('Fails when the 0th job result GET operation is absent', async () => {
402+
const oasDoc = clone(exampleDoc);
403+
delete (oasDoc.paths['/jobs/{jobID}/results/{outputID}/0'] as Record<string, unknown>).get;
404+
const violations = await spectral.run(oasDoc);
405+
406+
expect(violations).toContainViolation('/req/core/job-result-op-0th#get', 1);
407+
});
408+
});
409+
392410
describe('/req/core/job-results-async-one', () => {
393411
test('Fails when 200 response is absent', async () => {
394412
const oasDoc = clone(exampleDoc);
@@ -409,22 +427,32 @@ describe('/req/core/job-results-async-many', () => {
409427
});
410428
});
411429

412-
describe('/req/core/job-results-exception/invalid-query-parameter-value', () => {
430+
describe('/req/core/job-results-exception-invalid-query-parameter-value', () => {
431+
test('Fails when 400 response is absent', async () => {
432+
const oasDoc = clone(exampleDoc);
433+
delete (oasDoc.paths['/jobs/{jobID}/results'].get.responses as Record<string, unknown>)['400'];
434+
const violations = await spectral.run(oasDoc);
435+
436+
expect(violations).toContainViolation('/req/core/job-results-exception-invalid-query-parameter-value', 1);
437+
});
438+
});
439+
440+
describe('/req/core/job-results-exception-no-such-output', () => {
413441
test('Fails when 400 response is absent', async () => {
414442
const oasDoc = clone(exampleDoc);
415443
delete (oasDoc.paths['/jobs/{jobID}/results'].get.responses as Record<string, unknown>)['400'];
416444
const violations = await spectral.run(oasDoc);
417445

418-
expect(violations).toContainViolation('/req/core/job-results-exception/invalid-query-parameter-value', 1);
446+
expect(violations).toContainViolation('/req/core/job-results-exception-no-such-output', 1);
419447
});
420448
});
421449

422-
describe('/req/core/job-results-exception/no-such-job', () => {
450+
describe('/req/core/job-results-exception-no-such-job', () => {
423451
test('Fails when 404 response is absent', async () => {
424452
const oasDoc = clone(exampleDoc);
425453
delete (oasDoc.paths['/jobs/{jobID}/results'].get.responses as Record<string, unknown>)['404'];
426454
const violations = await spectral.run(oasDoc);
427455

428-
expect(violations).toContainViolation('/req/core/job-results-exception/no-such-job', 1);
456+
expect(violations).toContainViolation('/req/core/job-results-exception-no-such-job', 1);
429457
});
430458
});

src/specs/ogc-api/rulesets/processes-core.ts

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ import type { IFunctionResult, RulesetDefinition } from '@geonovum/standards-che
44
import { oas3_0 } from './formats';
55
import { truthy } from '@geonovum/standards-checker/spectral/functions';
66

7-
export const OGC_API_PROCESSES_CORE_URI = 'http://www.opengis.net/spec/ogcapi-processes-1/1.0/conf/core';
7+
export const OGC_API_PROCESSES_CORE_URI = 'http://www.opengis.net/spec/ogcapi-processes-1/2.0/conf/core';
88

99
export const OGC_API_PROCESSES_CORE_DOC_URI = 'https://docs.ogc.org/DRAFTS/18-062r3.html#req_core_';
1010

11+
// The v2.0 schemas are not yet published under https://schemas.opengis.net/ogcapi/processes/part1/2.0/
12+
// (18-062r3 is still a draft), so the `master` branch — which tracks the 2.0 revision — is the live source.
1113
export const SCHEMAS_URI_PREFIX = 'https://raw.githubusercontent.com/opengeospatial/ogcapi-processes/master/openapi/schemas/';
1214

1315
const processesCore: RulesetDefinition = {
14-
documentationUrl: 'http://www.opengis.net/spec/ogcapi-processes-1/1.0/req/core',
16+
documentationUrl: 'http://www.opengis.net/spec/ogcapi-processes-1/2.0/req/core',
1517
description: 'OGC API - Processes - Part 1: Core - Requirements Class "Core"',
1618
formats: [oas3_0],
1719
rules: {
@@ -167,7 +169,7 @@ const processesCore: RulesetDefinition = {
167169
function: truthy,
168170
},
169171
},
170-
'/req/core/process-exception/no-such-process': {
172+
'/req/core/process-exception-no-such-process': {
171173
given: '$.paths[?(@property.match(/^\\/processes\\/[^/]+$/))].get.responses',
172174
message: 'If the operation is executed using an invalid process identifier, the response SHALL be HTTP status code `404`.',
173175
documentationUrl: OGC_API_PROCESSES_CORE_DOC_URI + 'process-exception-no-such-process',
@@ -401,6 +403,28 @@ const processesCore: RulesetDefinition = {
401403
function: truthy,
402404
},
403405
},
406+
'/req/core/job-result-op-0th': {
407+
given: '$.paths',
408+
message: 'The server SHALL support the HTTP GET operation at the path `/jobs/{jobID}/results/{outputID}/0`.',
409+
documentationUrl: OGC_API_PROCESSES_CORE_DOC_URI + 'job-result-op-0th',
410+
severity: 'error',
411+
then: {
412+
function: hasPathMatch,
413+
functionOptions: {
414+
pattern: '^\\/jobs\\/[^/]+\\/results\\/[^/]+\\/0$',
415+
},
416+
},
417+
},
418+
'/req/core/job-result-op-0th#get': {
419+
given: '$.paths[?(@property.match(/^\\/jobs\\/[^/]+\\/results\\/[^/]+\\/0$/))]',
420+
message: 'The server SHALL support the HTTP GET operation at the path `/jobs/{jobID}/results/{outputID}/0`.',
421+
documentationUrl: OGC_API_PROCESSES_CORE_DOC_URI + 'job-result-op-0th',
422+
severity: 'error',
423+
then: {
424+
field: 'get',
425+
function: truthy,
426+
},
427+
},
404428
'/req/core/job-results-async-one': {
405429
given: '$.paths[?(@property.match(/^\\/jobs\\/[^/]+\\/results\\/[^/]+$/))].get.responses',
406430
message: 'A successful retrieval of a single result SHALL be reported with HTTP status code `200`.',
@@ -430,10 +454,30 @@ const processesCore: RulesetDefinition = {
430454
},
431455
],
432456
},
433-
'/req/core/job-results-exception/invalid-query-parameter-value': {
457+
'/req/core/job-results-exception-invalid-query-parameter-value': {
434458
given: '$.paths[?(@property.match(/^\\/jobs\\/[^/]+\\/results$/))].get.responses',
435459
message: 'If a query parameter has an invalid value, the response SHALL have HTTP status code `400`. {{error}}',
436-
documentationUrl: OGC_API_PROCESSES_CORE_DOC_URI + 'job-results-exception-invalid-query-parameter-value',
460+
documentationUrl: OGC_API_PROCESSES_CORE_DOC_URI + 'job-results-exception_invalid-query-parameter-value',
461+
severity: 'error',
462+
then: [
463+
{
464+
field: '400',
465+
function: truthy,
466+
},
467+
{
468+
field: '400',
469+
function: hasSchemaMatch,
470+
functionOptions: {
471+
schemaUri: SCHEMAS_URI_PREFIX + 'common-core/exception.yaml',
472+
},
473+
},
474+
],
475+
},
476+
'/req/core/job-results-exception-no-such-output': {
477+
given: '$.paths[?(@property.match(/^\\/jobs\\/[^/]+\\/results$/))].get.responses',
478+
message:
479+
'If the operation requests an output identifier that does not exist, the response SHALL have HTTP status code `400`. {{error}}',
480+
documentationUrl: OGC_API_PROCESSES_CORE_DOC_URI + 'job-results-exception-no-such-output',
437481
severity: 'error',
438482
then: [
439483
{
@@ -449,7 +493,7 @@ const processesCore: RulesetDefinition = {
449493
},
450494
],
451495
},
452-
'/req/core/job-results-exception/no-such-job': {
496+
'/req/core/job-results-exception-no-such-job': {
453497
given: '$.paths[?(@property.match(/^\\/jobs\\/[^/]+\\/results$/))].get.responses',
454498
message: 'If the job identifier is invalid, the response SHALL have HTTP status code `404`. {{error}}',
455499
documentationUrl: OGC_API_PROCESSES_CORE_DOC_URI + 'job-results-exception-no-such-job',
@@ -468,10 +512,10 @@ const processesCore: RulesetDefinition = {
468512
},
469513
],
470514
},
471-
'/req/core/job-results-exception/results-not-ready': {
515+
'/req/core/job-results-exception-results-not-ready': {
472516
given: '$.paths[?(@property.match(/^\\/jobs\\/[^/]+\\/results$/))].get.responses',
473517
message: 'If the job is still running, the response SHALL have HTTP status code `404`. {{error}}',
474-
documentationUrl: OGC_API_PROCESSES_CORE_DOC_URI + 'job-results-exception-results-not-ready',
518+
documentationUrl: OGC_API_PROCESSES_CORE_DOC_URI + 'job-results-exception_results-not-ready',
475519
severity: 'error',
476520
then: [
477521
{
@@ -487,10 +531,10 @@ const processesCore: RulesetDefinition = {
487531
},
488532
],
489533
},
490-
'/req/core/job-results-exception/results-not-available': {
534+
'/req/core/job-results-exception-results-not-available': {
491535
given: '$.paths[?(@property.match(/^\\/jobs\\/[^/]+\\/results$/))].get.responses',
492536
message: 'If no outputs are available, the response SHALL have HTTP status code `404`. {{error}}',
493-
documentationUrl: OGC_API_PROCESSES_CORE_DOC_URI + 'job-results-exception-results-not-available',
537+
documentationUrl: OGC_API_PROCESSES_CORE_DOC_URI + 'job-results-exception_results-not-available',
494538
severity: 'error',
495539
then: [
496540
{

src/specs/ogc-api/rulesets/processes-job-list.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import type { RulesetDefinition } from '@geonovum/standards-checker/spectral/cor
33
import { oas3_0 } from './formats';
44
import { truthy } from '@geonovum/standards-checker/spectral/functions';
55

6-
export const OGC_API_PROCESSES_JOB_LIST_URI = 'http://www.opengis.net/spec/ogcapi-processes-1/1.0/conf/job-list';
6+
export const OGC_API_PROCESSES_JOB_LIST_URI = 'http://www.opengis.net/spec/ogcapi-processes-1/2.0/conf/job-list';
77

88
export const OGC_API_PROCESSES_JOB_LIST_DOC_URI = 'https://docs.ogc.org/DRAFTS/18-062r3.html#req_job-list_';
99

1010
const processesJobList: RulesetDefinition = {
11-
documentationUrl: 'http://www.opengis.net/spec/ogcapi-processes-1/1.0/req/job-list',
11+
documentationUrl: 'http://www.opengis.net/spec/ogcapi-processes-1/2.0/req/job-list',
1212
description: 'OGC API - Processes - Part 1: Core - Requirements Class "Job list"',
1313
formats: [oas3_0],
1414
rules: {

src/specs/ogc-api/rulesets/processes-json.test.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,6 @@ describe('/req/json/definition', () => {
4141
expect(violations).toContainViolation('/req/json/definition', 1);
4242
});
4343

44-
test('Fails when sync execution result does not support media type "application/json".', async () => {
45-
const oasDoc = clone(exampleDoc);
46-
delete (oasDoc.components.responses.ExecuteSync.content as Record<string, unknown>)[APPLICATION_JSON_TYPE];
47-
const violations = await spectral.run(oasDoc);
48-
49-
expect(violations).toContainViolation('/req/json/definition', 1);
50-
});
51-
5244
test('Fails when job status does not support media type "application/json".', async () => {
5345
const oasDoc = clone(exampleDoc);
5446
delete (oasDoc.components.responses.JobStatus.content as Record<string, unknown>)[APPLICATION_JSON_TYPE];

src/specs/ogc-api/rulesets/processes-json.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,24 @@ import type { RulesetDefinition } from '@geonovum/standards-checker/spectral/cor
33
import { oas3_0 } from './formats';
44
import { schema } from '@geonovum/standards-checker/spectral/functions';
55

6-
export const OGC_API_PROCESSES_JSON_URI = 'http://www.opengis.net/spec/ogcapi-processes-1/1.0/conf/json';
6+
export const OGC_API_PROCESSES_JSON_URI = 'http://www.opengis.net/spec/ogcapi-processes-1/2.0/conf/json';
77

88
export const OGC_API_PROCESSES_JSON_DOC_URI = 'https://docs.ogc.org/DRAFTS/18-062r3.html#req_json_';
99

1010
const processesJson: RulesetDefinition = {
11-
documentationUrl: 'http://www.opengis.net/spec/ogcapi-processes-1/1.0/req/json',
11+
documentationUrl: 'http://www.opengis.net/spec/ogcapi-processes-1/2.0/req/json',
1212
description: 'OGC API - Processes - Part 1: Core - Requirements Class "JSON"',
1313
formats: [oas3_0],
1414
rules: {
15+
// v2 lists the endpoints that SHALL support application/json as: /, /conformance, /processes,
16+
// /processes/{processID}, /jobs/{jobID}. The execution endpoint is intentionally excluded — its
17+
// successful output is content-negotiated and need not be JSON.
1518
'/req/json/definition': {
1619
given: [
1720
'$.paths["/"].get.responses.200.content',
1821
'$.paths["/conformance"].get.responses.200.content',
1922
'$.paths[?(@property.match(/^\\/processes$/))].get.responses.200.content',
2023
'$.paths[?(@property.match(/^\\/processes\\/[^/]+$/))].get.responses.200.content',
21-
'$.paths[?(@property.match(/^\\/processes\\/[^/]+\\/execution$/))].post.responses.200.content',
2224
'$.paths[?(@property.match(/^\\/jobs\\/[^/]+$/))].get.responses.200.content',
2325
],
2426
message: '200-responses of the server SHALL support the "application/json" media type. {{error}}',

src/specs/ogc-api/rulesets/processes-ogc-process-description.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import type { RulesetDefinition } from '@geonovum/standards-checker/spectral/cor
33
import { oas3_0 } from './formats';
44

55
export const OGC_API_PROCESSES_OGC_PROCESS_DESCRIPTION_URI =
6-
'http://www.opengis.net/spec/ogcapi-processes-1/1.0/conf/ogc-process-description';
6+
'http://www.opengis.net/spec/ogcapi-processes-1/2.0/conf/ogc-process-description';
77

88
export const OGC_API_PROCESSES_OGC_PROCESS_DESCRIPTION_DOC_URI = 'https://docs.ogc.org/DRAFTS/18-062r3.html#req_ogc-process-description_';
99

1010
const processOgcProcessDescription: RulesetDefinition = {
11-
documentationUrl: 'http://www.opengis.net/spec/ogcapi-processes-1/1.0/req/ogc-process-description',
11+
documentationUrl: 'http://www.opengis.net/spec/ogcapi-processes-1/2.0/req/ogc-process-description',
1212
description: 'OGC API - Processes - Part 1: Core - Requirements Class "OGC Process Description"',
1313
formats: [oas3_0],
1414
rules: {
@@ -20,8 +20,9 @@ const processOgcProcessDescription: RulesetDefinition = {
2020
then: {
2121
function: hasSchemaMatch,
2222
functionOptions: {
23-
// TODO: Upgrade to v2.0 schema
24-
schemaUri: 'https://schemas.opengis.net/ogcapi/processes/part1/1.0/openapi/schemas/process.yaml',
23+
// v2.0 process schema. The schemas.opengis.net/.../2.0/ tree is not published yet (draft),
24+
// so the `master` branch — which tracks the 2.0 revision — is the live source.
25+
schemaUri: 'https://raw.githubusercontent.com/opengeospatial/ogcapi-processes/master/openapi/schemas/processes-core/process.yaml',
2526
},
2627
},
2728
},

0 commit comments

Comments
 (0)