Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added support for v3.1-RC #185

Merged
merged 3 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion RULES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
This project validates feeds up to version 3.0 of the [JSON Schemas](https://github.com/MobilityData/gbfs-json-schema).
This project validates feeds up to version 3.1-RC of the [JSON Schemas](https://github.com/MobilityData/gbfs-json-schema).
# Files presence
The validator will flag any missing file. It will inform the user if the missing file is required or not, as per the conditions in the GBFS version that it detects.

Expand Down
225 changes: 225 additions & 0 deletions gbfs-validator/__test__/fixtures/conditional_default_reserve_time.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
const fastify = require('fastify')

function build(opts = {}) {
const app = fastify(opts)

app.get('/gbfs.json', async function(request, reply) {
return {
last_updated: "2024-05-23T15:30:00Z",
ttl: 0,
version: '3.1-RC',
data: {
feeds: [
{
name: 'system_information',
url: `http://${request.hostname}/system_information.json`
},
{
name: 'station_information',
url: `http://${request.hostname}/station_information.json`
},
{
name: 'vehicle_types',
url: `http://${request.hostname}/vehicle_types.json`
},
{
name: 'system_pricing_plans',
url: `http://${request.hostname}/system_pricing_plans.json`
},
]

}
}
})

app.get('/system_information.json', async function(request, reply) {
return {
last_updated: "2024-05-23T15:30:00Z",
ttl: 0,
version: '3.1-RC',
data: {
system_id: 'shared_bike',
name: [
{
text: 'Shared Bike USA',
language: 'en'
}
],
timezone: 'Etc/UTC',
opening_hours: "Mo-Fr 08:00-17:00",
feed_contact_email: "[email protected]",
languages: ["en"]
}
}
})

app.get('/vehicle_types.json', async function(request, reply) {
return {
last_updated: "2024-05-23T15:30:00Z",
ttl: 0,
version: '3.1-RC',
data: {
vehicle_types: [
{
// default_reserve_time is required
vehicle_type_id: 'abc123',
form_factor: 'scooter',
propulsion_type: 'human',
name: [
{
text: 'Example Bicycle',
language: 'en'
}
],
//default_reserve_time: 30, // should throw error
return_type: ['any_station', 'free_floating'],
vehicle_assets: {
icon_url: 'https://www.example.com/assets/icon_bicycle.svg',
icon_url_dark:
'https://www.example.com/assets/icon_bicycle_dark.svg',
icon_last_modified: '2021-06-15'
},
default_pricing_plan_id: 'car_plan_2',
pricing_plan_ids: ['car_plan_2', 'car_plan_1']
},
{
// default_reserve_time is required
vehicle_type_id: 'efg456',
form_factor: 'car',
propulsion_type: 'electric',
name: [
{
text: 'Example Electric Car',
language: 'en'
}
],
default_reserve_time: 30,
max_range_meters: 100,
return_type: ['any_station', 'free_floating'],
vehicle_assets: {
icon_url: 'https://www.example.com/assets/icon_car.svg',
icon_url_dark: 'https://www.example.com/assets/icon_car_dark.svg',
icon_last_modified: '2021-06-15'
},
default_pricing_plan_id: 'car_plan_1',
pricing_plan_ids: ['car_plan_1', 'car_plan_2', 'car_plan_3']
},
{
// default_reserve_time is NOT required
vehicle_type_id: 'efg4567',
form_factor: 'car',
propulsion_type: 'electric',
name: [
{
text: 'Example Electric Car 2',
language: 'en'
}
],
//default_reserve_time: 30,
max_range_meters: 100,
return_type: ['any_station', 'free_floating'],
vehicle_assets: {
icon_url: 'https://www.example.com/assets/icon_car.svg',
icon_url_dark: 'https://www.example.com/assets/icon_car_dark.svg',
icon_last_modified: '2021-06-15'
},
default_pricing_plan_id: 'car_plan_2',
pricing_plan_ids: ['car_plan_2']
}
]
}
}
})

app.get('/system_pricing_plans.json', async function(request, reply) {
return {
last_updated: "2024-05-23T15:30:00Z",
ttl: 0,
version: '3.1-RC',
data: {
plans: [
{
plan_id: 'car_plan_1',
name: [
{
text: 'Basic',
language: 'en'
}
],
currency: 'USD',
price: 0,
is_taxable: false,
description: [
{
text: 'Basic plan',
language: 'en'
}
],
reservation_price_per_min: 3
},
{
plan_id: 'car_plan_2',
name: [
{
text: 'Basic 2',
language: 'en'
}
],
currency: 'USD',
price: 0,
is_taxable: false,
description: [
{
text: 'Basic plan',
language: 'en'
}
],
},
{
plan_id: 'car_plan_3',
name: [
{
text: 'Basic 3',
language: 'en'
}
],
currency: 'USD',
price: 0,
is_taxable: false,
description: [
{
text: 'Basic plan',
language: 'en'
}
],
reservation_price_flat_rate: 5
},
{
plan_id: 'car_plan_4',
name: [
{
text: 'Basic 4',
language: 'en'
}
],
currency: 'USD',
price: 0,
is_taxable: false,
description: [
{
text: 'Basic plan',
language: 'en'
}
],
reservation_price_flat_rate: 5,
reservation_price_per_min: 3 // this should throw an error
}
]
}
}
})

return app
}

module.exports = build
74 changes: 74 additions & 0 deletions gbfs-validator/__test__/gbfs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,80 @@ describe('conditional vehicle_types file', () => {
})
})

describe('required default_reserve_time on reservation price existing v3.1-RC', () => {

beforeAll(async () => {
gbfsFeedServer = require('./fixtures/conditional_default_reserve_time')()

await gbfsFeedServer.listen(serverOpts)

return gbfsFeedServer
});

afterAll(() => {
return gbfsFeedServer.close()
})

test('default_reserve_time should be required when reservation price is set', () => {
const url = `http://${gbfsFeedServer.server.address().address}:${
gbfsFeedServer.server.address().port
}`
const gbfs = new GBFS(`${url}/gbfs.json`);

expect.assertions(1);

return gbfs.validation().then(result => {

expect(result).toMatchObject({
summary: expect.objectContaining({
version: { detected: '3.1-RC', validated: '3.1-RC' },
hasErrors: true,
errorsCount: 3
}),
files: expect.arrayContaining([
expect.objectContaining(
{
file: 'vehicle_types.json',
languages: expect.arrayContaining([
expect.objectContaining({
errors: expect.arrayContaining([
expect.objectContaining({
instancePath: '/data/vehicle_types/0',
message:
"must have required property 'default_reserve_time'"
})
])
})
])
},
{
file: 'system_pricing_plans.json',
languages: expect.arrayContaining([
expect.objectContaining({
errors: expect.arrayContaining([
expect.objectContaining(
{
instancePath: '/data/plans/3',
schemaPath: '#/properties/data/properties/plans/items/dependencies/reservation_price_flat_rate/not',
message: "must NOT be valid"
},
{
instancePath: '/data/plans/3',
schemaPath: '#/properties/data/properties/plans/items/dependencies/reservation_price_per_min/not',
message: "must NOT be valid"
}
)
])
})
])
}
)
])
})
})
});
});

describe('conditional required vehicle_type_id', () => {
let gbfsFeedServer

Expand Down
15 changes: 15 additions & 0 deletions gbfs-validator/gbfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -790,8 +790,23 @@ class GBFS {
if (partial) {
addSchema.push(partial)
}
const pricingPlansIdsWithReservationPrice = pricingPlans.filter(p => p.reservation_price_flat_rate || p.reservation_price_per_min).map(p => p.plan_id)
if(pricingPlansIdsWithReservationPrice && pricingPlansIdsWithReservationPrice.length > 0) {
const partialReserveTime = getPartialSchema(
gbfsVersion,
'vehicle_types/default_reserve_time_require',
{
pricingPlansIdsWithReservationPrice
}
)

if (partialReserveTime) {
addSchema.push(partialReserveTime)
}
}
}


break
case 'system_pricing_plans':
if (hasBikesPricingPlanId) {
Expand Down
2 changes: 1 addition & 1 deletion gbfs-validator/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gbfs-validator",
"version": "1.0.8",
"version": "1.0.9",
"author": "MobilityData",
"main": "index.js",
"license": "MIT",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module.exports = ({ vehicleTypes }) => {
return {
$id: 'required_vehicle_types_available.json#',
$merge: {
source: {
$ref:
'https://github.com/MobilityData/gbfs/blob/v3.1-RC/gbfs.md#station_statusjson'
},
with: {
properties: {
data: {
properties: {
stations: {
items: {
properties: {
vehicle_types_available: {
items: {
properties: {
vehicle_type_id: {
enum: vehicleTypes.map(vt => vt.vehicle_type_id)
}
}
}
}
}
}
}
}
}
}
}
},
$patch: {
source: {
$ref:
'https://github.com/MobilityData/gbfs/blob/v3.1-RC/gbfs.md#station_statusjson'
},
with: [
{
op: 'add',
path: '/properties/data/properties/stations/items/required/0',
value: 'vehicle_types_available'
}
]
}
}
}
Loading
Loading