VSS to GraphQL Schema
Parse Vehicle Signal Specification tree structure to generate a special GraphQL schema.
Translation Rules and Basic Functionality
The vss2graphql_schema.py program loads the .vspec
file into the anytree
python structure using COVESA's vss-tools
functions. This structure
is then used to generate a GraphQL schema based on a special set of translation
rules.
Queries and Subscriptions
A GraphQL Query and Subscription fields are created for each root element from
anytree
structure. In case of having only the Vehicle
root element, it
generates the following structure on the schema:
# GraphQL schema generated file
type Query {
vehicle: Vehicle
}
type Subscription {
vehicle: Vehicle
}
Delivery Interval Subscription Parameter
If the command for subscription delivery interval is given,
a special GraphQL enumeration called SubscriptionDeliveryInterval
will be
generated and a parameter deliveryInterval
will be put on the Subscription
root field. So in case of having only the Vehicle
root signal on VSS, the
following structure will be generated on the schema:
# GraphQL schema generated file
enum SubscriptionDeliveryInterval {
"""Rate limited: 5s between updates"""
DELIVERY_INTERVAL_5_SECONDS
"""Rate limited: 1s between updates."""
DELIVERY_INTERVAL_1_SECOND
"""Get all the updates, no rate limit."""
REALTIME
}
type Subscription {
vehicle(deliveryInterval: SubscriptionDeliveryInterval! = DELIVERY_INTERVAL_5_SECONDS): Vehicle
}
Mutations and Inputs
Mutations are created based on actuators in vspec
files. If a branch has any
child of type actuator, a mutation will be created for that branch and the input
will be created to modify its actuator children.
# VSS file
Vehicle.Body.Door.IsOpen:
datatype: boolean
type: actuator
description: Door open or closed. True = Open. False = Close
Vehicle.Body.Door.IsLocked:
datatype: boolean
type: actuator
description: Door locked or unlocked. True = Open. False = Close
generates:
# GraphQL schema generated file
type Mutation{
setVehicleBodyDoor(input: Vehicle_Body_Door_Input): Vehicle_Body_Door
}
input Vehicle_Body_Door_Input{
IsOpen: Boolean
isLocked: Boolean
}
Type generation
VSS branches and leafs are translated to GraphQL types and fields on the schema. Branches generate custom types and leafs generates fields with a special type conversion (please see Data Types Translation subsection ).
Example:
# VSS file
Vehicle:
type: branch
description: Highlevel vehicle data.
Vehicle.Speed:
datatype: float
type: sensor
unit: km/h
description: Vehicle speed
Vehicle.Body:
type: branch
description: All body components.
Vehicle.Body.BodyType:
datatype: string
type: attribute
description: Body type code as defined by ISO 3779
Generates:
# GraphQL schema generated file
"""
Highlevel vehicle data.
"""
type Vehicle {
body: Vehicle_Body
""" Vehicle speed """
speed: Float
}
"""
All body components.
"""
type Vehicle_Body {
""" Body type code as defined by ISO 3779 """
bodyType: String
}
Data Scalar Types Translation
Scalar types are converted automatically to the respective GraphQL types, as the table below shows. If nothing is specified, the native types will be used for conversion, but there is an option to automatically generate custom scalars.
VSS datatype | GraphQL Native Type | Custom GraphQL Scalars |
---|---|---|
int8 | Int | Int8 |
uint8 | Int | UInt8 |
int16 | Int | Int16 |
uint16 | Int | UInt16 |
int32 | Int | Int32 |
uint32 | Int | UInt32 |
int64 | String | Int64 |
uint64 | String | UInt64 |
float | Float | Float |
double | Float | Float |
boolean | Boolean | Boolean |
string | String | String |
Array VSS datatypes are translated to GraphQL lists.
Enumerations
VSS Enumerations are converted to GraphQL Enums the following way:
# VSS file
Vehicle.Body.RefuelPosition:
datatype: string
type: attribute
enum: ["front_left", "front_right", "middle_left", "middle_right", "rear_left", "rear_right"]
description: Location of the fuel cap or charge port
generates:
# GraphQL schema generated file
"""
Location of the fuel cap or charge port
"""
type Vehicle_Body {
""" Location of the fuel cap or charge port """
refuelPosition: Vehicle_Body_RefuelPosition_Enum
}
enum Vehicle_Body_RefuelPosition_Enum {
FRONT_LEFT
FRONT_RIGHT
MIDDLE_LEFT
MIDDLE_RIGHT
REAR_LEFT
REAR_RIGHT
}
Note: Enum values are transformed: all non-alphanumeric characters are transformed into
_
, all letters are uppercased and another underscore is put on the beginning if it starts with a number.
Min and Max values
Using range
directives in GraphQL schema it is possible to reflect min
and
max
values specified in VSS in the following manner:
# VSS file
Vehicle.CurrentLocation.Latitude:
datatype: double
type: sensor
min: -90
max: 90
unit: degrees
description: Current latitude of vehicle.
generates:
# GraphQL schema generated file
# This line is generated only once
directive @range(min: Float, max: Float) on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
"""
The current latitude and longitude of the vehicle.
"""
type Vehicle_CurrentLocation {
""" Current latitude of vehicle. """
latitude: Float @range(min: -90.0, max: 90.0)
}
hasPermission Directive
To regulate access to GraphQL schema fields for clients,
hasPermissions
directive will be created and used. All VSS leafs
will receive hasPermissions
declaration of type read, leafs of
type actuator will in addition receive hasPermissions
declaration
of type write:
# GraphQL schema generated file
# This directive and enum is generated only once
enum HasPermissionsDirectivePolicy {
RESOLVER
THROW
}
directive @hasPermissions(permissions: [String!]!, policy: HasPermissionsDirectivePolicy) on FIELD_DEFINITION | OBJECT | INPUT_FIELD_DEFINITION
# When an input is generated to a mutation, it receives write permission
input Vehicle_MyBranch {
myField: Float @hasPermissions(permissions: ["Vehicle.MyBranch.MyField_WRITE"])
}
# When a type field is generated, it receives read permission
type Vehicle_MyBranch {
myField: Float @hasPermissions(permissions: ["Vehicle.MyBranch.MyField_READ"])
}
Franca to VSS Layer Input
This tool can use franca-based Layer files to determine behavior. For more information on franca-based layers please see the layer readme used to test this tool.
Filtering
When given the root layer file on the execution command the tree will be
filtered according to the same tree structure on the layer file. For instance
if you want to generate the schema for Vehicle.CurrentLocation.Latitude
you
will have to have a layer file with the following structure:
# Layer file
Vehicle:
CurrentLocation:
Latitude:
<your layer info>
Mutations
Mutations also require a special _FrancaIDL structure informing write
permissions in addition to the actuator
datatype on VSS. For instance if you
want to have MySignal
(which is already an actuator) to have a mutation, you
will need to add this _francaIDL
write method:
# Layer file
MySignal:
_francaIDL:
methods:
write: # This indicates write access, allowing
Lists
By specifying the branch as a list in the layer file, that branch will be set
as a list in the schema, and all the children (and grandchildren and so on) will
have an id: ID!
field, to specify what index from the list you want to access.
For instance:
# VSS file
Vehicle.Chassis.Axle:
type: branch
description: Axle signals
Vehicle.Chassis.Axle.myAxleSignal:
datatype: string
type: sensor
description: myAxleSignal
# Layer file
Vehicle:
Chassis:
- Axle:
myAxleSignal:
<your layer info>
# GraphQL schema generated file
type Vehicle_Chassis {
axle: [Vehicle_Chassis_Axle]
}
type Vehicle_Chassis_Axle {
myAxleSignal: String
id: ID!
}
Parent Attributes on mutations
On the layer file you can also point to a leaf and say it to resolve in the
immediate parent by using the _parentAttribute
structure. For instance if you
want to MyParentAttr1
to be resolved in the mutation of Vehicle:
# Layer file
Vehicle:
MyResolvableBranch:
_francaIDL:
methods:
write:
<your layer info>
MyParentAttr1:
_parentAttribute:
MyParentAttr2:
_parentAttribute:
MyParentAttr3:
_parentAttribute:
MyLeaf:
_francaIDL:
methods:
write:
<your layer info>
Generates:
# GraphQL schema generated file
type Mutation {
setVehicle(input: Vehicle_Input!): Vehicle
}
input Vehicle_Input {
MyLeaf: Boolean
myResolvableBranch: Vehicle_MyResolvableBranch_Input
input Vehicle_MyResolvableBranch_Input {
MyParentAttr1: Boolean
MyParentAttr2: Boolean
MyParentAttr3: Boolean
}
Getting Started
For this project you will need to have:
- Python 3.8.5 or later
- Pip
- Pipenv
Python Installation On Linux (or mac)
If you don't have Python installed already I suggest you to use pyenv to install Python by using this following command:
pyenv install <desired py version>
Then in this repo's folder there should be a .python-version
file that describes
the version of python to be used, in our repo there is this file with the version 3.8.5
written. If you want to create this file by yourself and use a specific version you can run:
pyenv local <desired py version>
Pip Installation on Linux
Make sure you have pip installed too. To check you can run
python3 -m pip -V
and see the version of your pip. Make sure your pip is updated with
python3 -m pip install --upgrade pip
If you don't have you can install with
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python3 get-pip.py --force-reinstall
Pipenv Installation on Linux
To install Pipenv just run
python3 -m pip install --user pipenv
And pipenv will be installed as a python package under the --user
flag and you
will be able to run python3 -m pipenv --help
.
It is a good practice to set on your .bashrc
(or other, see note 2), the
variable which configures PIPENV
to always create the virtual environment
inside a .env
folder the project.
echo "export PIPENV_VENV_IN_PROJECT=1" >> ~/.bashrc
source ~/.bashrc
Note 1: When you install pipenv using pip with
--user
flag, you are installing pipenv under a folder.local
under your home directory, so if your system does not recognize pipenv as a command, it's maybe because your$PATH
variable does not see this.local
folder. One alternative is to add this line to your.bashrc
(or similar please see note 2) file as follows:echo "export PATH=$PATH:$HOME/.local/bin" >> ~/.bashrc source ~/.bashrcand you will be able to run pipenv directly. If you prefer you can always just use:
python3 -m pipenvwhen you want to just run pipenv
Note 2: Every time
.bashrc
is referred here in this file, we are talking about the file that runs when your terminal is open, this file may change depending on the system and what shell you are using, this may be~/.bash_profile
or./zsh
.
Installation of VSS2GraphQL_Schema
To install the project and dependencies you can run the following command:
pipenv sync
This command will install this package and its dependencies under your pipenv
isolated environment (.env
folder if you followed Note 1). Then you can run
commands of this environment with pipenv run <command in environment>
.
Execution of VSS2GraphQL_Schema
To run the program please cd
to root path of this project and run:
pipenv run vss2graphql_schema --help
Regex filter and match
These filters will serve to select or remove vss nodes from the schema. The
filter will be used in every node of vss (branches and leafs). Regex filters
will remove nodes (and its children) with qualified name (full lenght name
separated by _
Eg: Vehicle_Speed) and match will only include nodes that
matches the regex pattern send.
Examples:
# Including only Vehicle_ADAS (notice that on match you need to match intermediate branches surrounded with ^$ (regex way of saying to match the exact string))
pipenv run vss2graphql_schema --output=resources/schema.graphql --regex-match="^Vehicle$|Vehicle_ADAS" ../resources/spec/VehicleSignalSpecification.vspec
# Excluding Vehicle_ADAS and Vehicle_Powertrain_Transmission (and everything under those branches)
pipenv run vss2graphql_schema --output=resources/schema.graphql --regex-filter="Vehicle_ADAS|Vehicle_Powertrain_Transmission" ../resources/spec/VehicleSignalSpecification.vspec
Note: If the file is empty while using regex match, please consider that you may be not matching any complete path to a leaf with your regex pattern.
Contribution to the Development of VSS2GraphQL_Schema
To install dev packages one may run:
pipenv sync -d
Linting
One may format with autopep8 with command-line:
autopep8 --in-place --aggressive --aggressive file.py
And to check linting you can run:
pipenv run flake8 --config setup.cfg file.py
Check Typing
To use mypy you can run:
pipenv run mypy --config-file ./setup.cfg file.py
Tests
To run nosetests you can run:
pipenv run nosetests --with-doctest file.py