diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml new file mode 100644 index 00000000..e70bf5d4 --- /dev/null +++ b/.github/workflows/dependabot-automerge.yml @@ -0,0 +1,23 @@ +name: Dependabot auto-merge +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + steps: + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v1 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Enable auto-merge for Dependabot PRs + if: ${{contains(steps.metadata.outputs.dependency-names, 'my-dependency') && steps.metadata.outputs.update-type == 'version-update:semver-patch'}} + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/validator-test.yml b/.github/workflows/validator-test.yml new file mode 100644 index 00000000..f3f9634a --- /dev/null +++ b/.github/workflows/validator-test.yml @@ -0,0 +1,39 @@ +name: Test validator + +on: + pull_request: + branches: [ master ] + +jobs: + test-validator: + runs-on: ubuntu-latest + steps: + - name: Checkout data-models + uses: actions/checkout@v2 + with: + path: data-models + - name: Checkout data-model-validator + uses: actions/checkout@v2 + with: + path: data-model-validator + repository: openactive/data-model-validator + + - name: Use Node.js 14.x + uses: actions/setup-node@v1 + with: + node-version: 14 + + - name: Install data-models + run: npm install + working-directory: data-models + - name: Install data-model-validator + run: npm install + working-directory: data-model-validator + + - name: Update data-model-validator to reference local data-models + run: npm install file:../data-models + working-directory: data-model-validator + + - name: Test + run: npm test + working-directory: data-model-validator diff --git a/package-lock.json b/package-lock.json index 79f1e560..be291c21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@openactive/data-models", - "version": "2.0.295", + "version": "2.0.303", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 078f5a2e..12c09e8e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@openactive/data-models", - "version": "2.0.295", + "version": "2.0.303", "description": "Data models used to drive that OpenActive validator, developer documentation, and model libraries", "homepage": "https://www.openactive.io", "author": "OpenActive Community ", diff --git a/src/getExamplesWithContent-spec.js b/src/getExamplesWithContent-spec.js index c596b521..816f4946 100644 --- a/src/getExamplesWithContent-spec.js +++ b/src/getExamplesWithContent-spec.js @@ -2,18 +2,33 @@ const getExamplesWithContent = require('./getExamplesWithContent'); describe('getExamplesWithContent', () => { it('should throw if passed an invalid spec version', async () => { - expectAsync(getExamplesWithContent('0.0')).toBeRejected(); + expect(() => getExamplesWithContent('0.0')).toThrow(); }); it('should return valid examples as JSON', async () => { - const examples = await getExamplesWithContent('2.0'); + const examples = getExamplesWithContent('2.0'); expect(typeof examples).toBe('object'); expect(examples instanceof Array).toBe(true); }); it('should return valid examples as JSON with a version alias', async () => { - const examples = await getExamplesWithContent('latest'); + const examples = getExamplesWithContent('latest'); expect(typeof examples).toBe('object'); expect(examples instanceof Array).toBe(true); }); + + it('should use remote fetcher for remote examples', async () => { + const mockFetcher = x => `Fetcher:${x}`; + + const examples = getExamplesWithContent('latest', mockFetcher); + expect(typeof examples).toBe('object'); + expect(examples instanceof Array).toBe(true); + + const remoteExamples = examples.filter(x => x.file.indexOf('http') === 0); + expect(remoteExamples).toBeNonEmptyArray(); + + for (const { file, data } of remoteExamples) { + expect(data).toBe(mockFetcher(file)); + } + }); }); diff --git a/src/getExamplesWithContent.js b/src/getExamplesWithContent.js index f018a0b9..9c2538bb 100644 --- a/src/getExamplesWithContent.js +++ b/src/getExamplesWithContent.js @@ -1,19 +1,30 @@ -const fs = require('fs').promises; +const fs = require('fs'); const getExamples = require('./getExamples'); const EXAMPLES_DIRECTORY = `${__dirname}/../versions/2.x/examples/`; +const EXAMPLES_BASE_URL = 'https://openactive.io/data-models/versions/2.x/examples/'; -const getExamplesWithContent = async (version) => { + +const getExamplesWithContent = (version, fetcherForRemoteExamplesSync) => { const examples = getExamples(version); + const flattenedExamples = examples.flatMap(x => x.exampleList.map(example => ({ category: x.name, ...example }))); const output = []; - for (const { file, name } of examples.flatMap(x => x.exampleList)) { - // Only include local examples - if (file.indexOf('http') !== 0) { - const data = JSON.parse(await fs.readFile(`${EXAMPLES_DIRECTORY}${file}`, { encoding: 'utf8' })); + for (const example of flattenedExamples) { + if (example.file.indexOf('http') !== 0) { + // Local example file + const data = JSON.parse(fs.readFileSync(`${EXAMPLES_DIRECTORY}${example.file}`, { encoding: 'utf8' })); + output.push({ + data, + url: `${EXAMPLES_BASE_URL}${example.file}`, + ...example, + }); + } else if (fetcherForRemoteExamplesSync) { + // Remote example file + const data = fetcherForRemoteExamplesSync(`${example.file}`); output.push({ - name, - file, data, + url: example.file, + ...example, }); } } diff --git a/versions/2.x/models/BookingService.json b/versions/2.x/models/BookingService.json index d91f1f58..05113ce9 100644 --- a/versions/2.x/models/BookingService.json +++ b/versions/2.x/models/BookingService.json @@ -15,7 +15,8 @@ "BResponse": "response", "BResponseOrderItemError": "response", "OrderProposalPatch": "request", - "OrderPatch": "request" + "OrderPatch": "request", + "DatasetSite": "datasetSite" }, "imperativeConfiguration": { "request": { @@ -28,6 +29,21 @@ "name" ], "recommendedFields": [] + }, + "datasetSite": { + "requiredOptions": [ + { + "description": [ + "A `BookingService` must include a `name`, unless it is a bespoke build, in which case only `hasCredential` is required." + ], + "options": [ + "name", + "hasCredential" + ] + } + ], + "requiredFields": [], + "recommendedFields": [] } }, "inSpec": [ diff --git a/versions/2.x/models/Dataset.json b/versions/2.x/models/Dataset.json index 026ef56c..a7a75e41 100644 --- a/versions/2.x/models/Dataset.json +++ b/versions/2.x/models/Dataset.json @@ -3,6 +3,34 @@ "derivedFrom": "https://schema.org/Dataset", "hasId": true, "sampleId": "https://opendata.fusion-lifestyle.com/OpenActive/", + "validationMode": { + "RPDEFeed": "noRequired", + "BookableRPDEFeed": "noRequired", + "DataCatalog": "noRequired", + "C1Request": "noRequired", + "C1Response": "noRequired", + "C1ResponseOrderItemError": "noRequired", + "C2Request": "noRequired", + "C2Response": "noRequired", + "C2ResponseOrderItemError": "noRequired", + "PRequest": "noRequired", + "PResponse": "noRequired", + "PResponseOrderItemError": "noRequired", + "BRequest": "noRequired", + "BResponse": "noRequired", + "BResponseOrderItemError": "noRequired", + "OrderProposalPatch": "noRequired", + "OrderPatch": "noRequired", + "OrdersFeed": "noRequired", + "OrderProposalsFeed": "noRequired", + "OrderStatus": "noRequired" + }, + "imperativeConfiguration": { + "noRequired": { + "requiredFields": [], + "recommendedFields": [] + } + }, "requiredFields": [ "id", "type", diff --git a/versions/2.x/models/FacilityUse.json b/versions/2.x/models/FacilityUse.json index 097e875f..5002af4f 100644 --- a/versions/2.x/models/FacilityUse.json +++ b/versions/2.x/models/FacilityUse.json @@ -58,7 +58,8 @@ "shallNotInclude": [ "offers", "event", - "provider" + "provider", + "individualFacilityUse" ] } }, diff --git a/versions/2.x/models/PropertyValue.json b/versions/2.x/models/PropertyValue.json index f20384a1..7d01eaef 100644 --- a/versions/2.x/models/PropertyValue.json +++ b/versions/2.x/models/PropertyValue.json @@ -32,6 +32,10 @@ "type", "name", "description" + ], + "shallNotInclude": [ + "value", + "propertyID" ] }, "additionalProperty": { @@ -39,6 +43,10 @@ "type", "name", "value" + ], + "shallNotInclude": [ + "description", + "propertyID" ] }, "orderItemIntakeFormResponse": { @@ -46,6 +54,10 @@ "type", "propertyID", "value" + ], + "shallNotInclude": [ + "name", + "description" ] } } diff --git a/versions/2.x/models/WebAPI.json b/versions/2.x/models/WebAPI.json index 5eeb31aa..e020318f 100644 --- a/versions/2.x/models/WebAPI.json +++ b/versions/2.x/models/WebAPI.json @@ -2,6 +2,34 @@ "type": "WebAPI", "derivedFrom": "https://pending.schema.org/WebAPI", "hasId": false, + "validationMode": { + "RPDEFeed": "noRequired", + "BookableRPDEFeed": "noRequired", + "DataCatalog": "noRequired", + "C1Request": "noRequired", + "C1Response": "noRequired", + "C1ResponseOrderItemError": "noRequired", + "C2Request": "noRequired", + "C2Response": "noRequired", + "C2ResponseOrderItemError": "noRequired", + "PRequest": "noRequired", + "PResponse": "noRequired", + "PResponseOrderItemError": "noRequired", + "BRequest": "noRequired", + "BResponse": "noRequired", + "BResponseOrderItemError": "noRequired", + "OrderProposalPatch": "noRequired", + "OrderPatch": "noRequired", + "OrdersFeed": "noRequired", + "OrderProposalsFeed": "noRequired", + "OrderStatus": "noRequired" + }, + "imperativeConfiguration": { + "noRequired": { + "requiredFields": [], + "recommendedFields": [] + } + }, "requiredFields": [ "type", "name",