Skip to content

Commit

Permalink
don't try and use jsonschema for this
Browse files Browse the repository at this point in the history
Jsonschema is a lot of things but one thing it isn't is a definer of
schema behavior based on values in the document. That's the key to all
of this - the document specifying a subschema for part of its content -
so we're just going to do it in code instead.

That means
- In the protocol schema, dynamically subschema'd objects get typed as
objects
- We get a new typescript validator to handle it all
- Also update python for this but it matters a bit less because the
python defines what is acceptable anyway, so there's no point trying to
be future proof here when downstream consumers will have to change
  • Loading branch information
sfoster1 committed Oct 30, 2023
1 parent 6e3c0db commit e51e727
Show file tree
Hide file tree
Showing 13 changed files with 535 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,7 @@ def _make_v8_json_protocol(
metadata=SD_Metadata(),
robot=Robot(model="OT-2 Standard", deckId="ot2_standard"),
labwareDefinitions=labware_definitions,
labwareSchemaId="opentronsLabwareSchemaV2",
labwareDefinitionSchemaId="opentronsLabwareSchemaV2",
commandSchemaId="opentronsCommandSchemaV8",
commands=commands,
liquidSchemaId="opentronsLiquidSchemaV1",
Expand Down
2 changes: 1 addition & 1 deletion robot-server/tests/integration/protocols/simpleV8.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"displayColor": "#7332a8"
}
},
"labwareSchemaId": "opentronsLabwareSchemaV2",
"labwareDefinitionSchemaId": "opentronsLabwareSchemaV2",
"labwareDefinitions": {
"opentrons/opentrons_1_trash_1100ml_fixed/1": {
"ordering": [["A1"]],
Expand Down
92 changes: 46 additions & 46 deletions shared-data/commandAnnotation/schemas/1.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$id": "opentronsCommandAnnotationSchemaV1",
"$id": "opentronsCommandAnnotationSchemaV1#",
"$schema": "http://json-schema.org/draft-07/schema#",
"$defs": {
"baseAnnotation": {
Expand All @@ -18,53 +18,53 @@
"description": "The type of annotation (for machine parsing)",
"type": "string"
}
},
"secondOrderCommand": {
"description": "Annotates a group of atomic commands which were the direct result of a second order command (e.g. transfer, consolidate, mix)",
"allOf": [{ "$ref": "#/$defs/baseAnnotation" }],
"type": "object",
"required": [
"annotationType",
"commandKeys",
"params",
"machineReadableName"
],
"properties": {
"annotationType": {
"type": "string",
"enum": ["secondOrderCommand"]
},
"params": {
"description": "key value pairs of the parameters that were passed to the second order command that this annotates",
"type": "object"
},
"machineReadableName": {
"description": "The name of the second order command in the form that the generating software refers to it. (e.g. 'transfer', 'thermocyclerStep')",
"type": "string"
},
"userSpecifiedName": {
"description": "The optional user-specified name of the second order command",
"type": "string"
},
"userSpecifiedDescription": {
"description": "The optional user-specified description of the second order command",
"type": "string"
}
}
},
"custom": {
"description": "Annotates a group of atomic commands in some manner that Opentrons software does not anticipate or originate",
"allOf": [{ "$ref": "#/$defs/baseAnnotation" }],
"type": "object",
"required": ["annotationType", "commandKeys"],
"properties": {
"annotationType": {
"type": "string",
"enum": ["custom"]
}
}
},
"secondOrderCommand": {
"description": "Annotates a group of atomic commands which were the direct result of a second order command (e.g. transfer, consolidate, mix)",
"allOf": [{ "$ref": "#/$defs/baseAnnotation" }],
"type": "object",
"required": [
"annotationType",
"commandKeys",
"params",
"machineReadableName"
],
"properties": {
"annotationType": {
"type": "string",
"enum": ["secondOrderCommand"]
},
"params": {
"description": "key value pairs of the parameters that were passed to the second order command that this annotates",
"type": "object"
},
"additionalProperties": true
"machineReadableName": {
"description": "The name of the second order command in the form that the generating software refers to it. (e.g. 'transfer', 'thermocyclerStep')",
"type": "string"
},
"userSpecifiedName": {
"description": "The optional user-specified name of the second order command",
"type": "string"
},
"userSpecifiedDescription": {
"description": "The optional user-specified description of the second order command",
"type": "string"
}
}
},
"custom": {
"description": "Annotates a group of atomic commands in some manner that Opentrons software does not anticipate or originate",
"allOf": [{ "$ref": "#/$defs/baseAnnotation" }],
"type": "object",
"required": ["annotationType", "commandKeys"],
"properties": {
"annotationType": {
"type": "string",
"enum": ["custom"]
}
},
"additionalProperties": true
}
},
"oneOf": [
Expand Down
85 changes: 85 additions & 0 deletions shared-data/js/__tests__/protocolValidation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/** Ensure that we can parse all our fixture json protocols, which also

Check failure on line 1 in shared-data/js/__tests__/protocolValidation.test.ts

View workflow job for this annotation

GitHub Actions / js checks

Expected space or tab before '*/' in comment

Check failure on line 1 in shared-data/js/__tests__/protocolValidation.test.ts

View workflow job for this annotation

GitHub Actions / js checks

Expected space or tab before '*/' in comment
* ensures that our protocol schemas are correct*/
import path from 'path'
import glob from 'glob'
import { validate } from '../protocols'
import { omit } from 'lodash'

import type * as ProtocolSchemas from '../../protocol'

Check failure on line 8 in shared-data/js/__tests__/protocolValidation.test.ts

View workflow job for this annotation

GitHub Actions / js checks

'ProtocolSchemas' is defined but never used

Check failure on line 8 in shared-data/js/__tests__/protocolValidation.test.ts

View workflow job for this annotation

GitHub Actions / js checks

'ProtocolSchemas' is defined but never used

const relRoot = path.join(__dirname, '../../protocol/fixtures/')

const protocolFixtures4 = glob.sync(
path.join(__dirname, '../../protocol/fixtures/4/*.json')
)
const protocolFixtures5 = glob.sync(
path.join(__dirname, '../../protocol/fixtures/5/*.json')
)
const protocolFixtures6 = glob.sync(
path.join(__dirname, '../../protocol/fixtures/6/*.json')
)
const protocolFixtures7 = glob.sync(
path.join(__dirname, '../../protocol/fixtures/7/*.json')
)
const protocolFixtures8 = glob.sync(
path.join(__dirname, '../../protocol/fixtures/8/*.json')
)

describe('check that all fixtures can validate', () => {
const protocolPaths = [
...protocolFixtures4,
...protocolFixtures5,
...protocolFixtures6,
...protocolFixtures7,
...protocolFixtures8,
]
protocolPaths.forEach(protocolPath =>
it(`${path.relative(relRoot, protocolPath)}`, () => {
const protocol = require(protocolPath)
return validate(protocol)
})
)
})

describe('test that mutating v8 fixtures causes failure', () => {
const base = require('../../protocol/fixtures/8/simpleV8.json')
it('should fail validation with no schema spec', () => {
const mangled = omit(base, '$otSharedSchema')
expect.assertions(1)
return expect(validate(mangled)).rejects.toMatchObject([
{ keyword: 'Invalid protocol schema requested' },
])
})
it('should fail validation with a schema spec from the future', () => {
const mangled = { ...base, $otSharedSchema: 'opentronsProtocolSchemaV9' }
expect.assertions(1)
return expect(validate(mangled)).rejects.toMatchObject([
{ keyword: 'Invalid protocol schema requested' },
])
})
it('should fail validation with a mangled schema spec', () => {
const mangled = { ...base, $otSharedSchema: 'asdhasda' }
expect.assertions(1)
return expect(validate(mangled)).rejects.toMatchObject([
{ keyword: 'Invalid protocol schema requested' },
])
})
it('should fail validation with a mangled command schema spec', () => {
const mangled = { ...base, commandSchemaId: 'asdasd' }
return expect(validate(mangled)).rejects.toMatchObject([
{ keyword: 'Invalid command schema requested' },
])
})
it('should fail validation with a mangled liquid schema spec', () => {
const mangled = { ...base, liquidSchemaId: 'asdhasdas' }
return expect(validate(mangled)).rejects.toMatchObject([
{ keyword: 'Invalid liquid schema requested' },
])
})
it('should fail validation with a mangled command annotation schema spec', () => {
const mangled = { ...base, commandAnnotationSchemaId: 'asdasd' }
return expect(validate(mangled)).rejects.toMatchObject([
{ keyword: 'Invalid command annotation schema requested' },
])
})
})
Loading

0 comments on commit e51e727

Please sign in to comment.