Skip to content
Draft
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
78 changes: 63 additions & 15 deletions note.template.req.notecard.api.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,50 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/blues/notecard-schema/master/note.template.req.notecard.api.json",
"title": "note.template Request Application Programming Interface (API) Schema",
"description": "By using the note.template request with any .qo/.qos Notefile, developers can provide the Notecard with a schema of sorts to apply to future Notes added to the Notefile. This template acts as a hint to the Notecard that allows it to internally store data as fixed-length binary records rather than as flexible JSON objects which require much more memory. Using templated Notes in place of regular Notes increases the storage and sync capability of the Notecard by an order of magnitude.",
"description": "By using the `note.template` request with any `.qo`/`.qos` Notefile, developers can provide the Notecard with a schema of sorts to apply to future Notes added to the Notefile. This template acts as a hint to the Notecard that allows it to internally store data as fixed-length binary records rather than as flexible JSON objects which require much more memory. Using templated Notes in place of regular Notes increases the storage and sync capability of the Notecard by an order of magnitude.",
"type": "object",
"skus": ["CELL", "CELL+WIFI", "LORA", "WIFI"],
"version": "0.2.1",
"apiVersion": "9.1.1",
"properties": {
"file": {
"description": "Name of the notefile to add the note to",
"type": "string"
},
"template": {
"description": "Template for the note",
"type": "object"
},
"body": {
"description": "Template for the note body",
"type": "object"
},
"cmd": {
"description": "Command for the Notecard (no response)",
"const": "note.template"
},
"req": {
"description": "Request for the Notecard (expects response)",
"const": "note.template"
},
"file": {
"description": "The name of the Notefile to which the template will be applied.",
"type": "string"
},
"body": {
"description": "A sample JSON body that specifies field names and values as \"hints\" for the data type. Possible data types are: boolean, integer, float, and string. See [Understanding Template Data Types](https://dev.blues.io/notecard/notecard-walkthrough/low-bandwidth-design/#understanding-template-data-types) for an explanation of type hints and explanations.",
"type": "object"
},
"length": {
"description": "The maximum length of a `payload` (in bytes) that can be sent in Notes for the template Notefile. As of v3.2.1 `length` is not required, and payloads can be added to any template-based Note without specifying the payload length.",
"type": "integer",
"minimum": -1
},
"verify": {
"description": "If `true`, returns the current template set on a given Notefile.",
"type": "boolean"
},
"format": {
"description": "By default all Note templates automatically include metadata, including a timestamp for when the Note was created, various fields about a device's location, as well as a timestamp for when the device's location was determined.\n\nBy providing a `format` of `\"compact\"` you tell the Notecard to omit this additional metadata to save on storage and bandwidth. The use of `format: \"compact\"` is required for Notecard LoRa and a Notecard paired with Starnote.\n\nWhen using `\"compact\"` templates, you may include the following keywords in your template to add in fields that would otherwise be omitted: `_lat`, `_lon`, `_ltime`, `_time`. See [Creating Compact Templates](https://dev.blues.io/notecard/notecard-walkthrough/low-bandwidth-design/#creating-compact-templates) to learn more.",
"type": "string"
},
"port": {
"description": "This argument is required on Notecard LoRa and a Notecard paired with Starnote, but ignored on all other Notecards.\n\nA port is a unique integer in the range 1–100, where each unique number represents one Notefile. This argument allows the Notecard to send a numerical reference to the Notefile over the air, rather than the full Notefile name.\n\nThe port you provide is also used in the \"frame port\" field on LoRaWAN gateways.",
"type": "integer",
"minimum": 1,
"maximum": 100
},
"delete": {
"description": "Set to `true` to delete all pending Notes using the template if one of the following scenarios is also true:\n\nConnecting via non-NTN (e.g. cellular or Wi-Fi) communications, but attempting to sync NTN-compatible Notefiles.\n\nor\n\nConnecting via NTN (e.g. satellite) communications, but attempting to sync non-NTN-compatible Notefiles.\n\nRead more about this feature in [Starnote Best Practices](https://dev.blues.io/starnote/starnote-best-practices/#define-ntn-vs-non-ntn-templates).",
"type": "boolean"
}
},
"oneOf": [
Expand All @@ -49,6 +71,32 @@
}
],
"additionalProperties": false,
"version": "0.1.1",
"apiVersion": "9.1.1"
"samples": [
{
"title": "Provide Schema",
"json": "{\"req\":\"note.template\",\"file\":\"readings.qo\",\"body\":{\"new_vals\":true,\"temperature\":14.1,\"humidity\":11,\"pump_state\":\"4\"}}"
},
{
"title": "Compact Schema for LoRa",
"json": "{\"req\":\"note.template\",\"file\":\"readings.qo\",\"body\":{\"new_vals\":true,\"temperature\":14.1,\"humidity\":11,\"pump_state\":\"4\"},\"format\":\"compact\",\"port\":50}"
},
{
"title": "Compact Schema with Additional Fields",
"json": "{\"req\":\"note.template\",\"file\":\"readings.qo\",\"body\":{\"temperature\":14.1,\"_lat\":14.1,\"_lon\":14.1,\"_ltime\":14,\"_time\":14},\"format\":\"compact\",\"port\":50}"
},
{
"title": "Request Current Template",
"json": "{\"req\":\"note.template\",\"file\":\"readings.qo\",\"verify\":true}"
}
],
"annotations": [
{
"title": "note",
"description": "Read about [Working with Note Templates](https://dev.blues.io/notecard/notecard-walkthrough/low-bandwidth-design/#working-with-note-templates) for additional information."
},
{
"title": "note",
"description": "See examples of `note.template` usage in [these accelerator projects](https://dev.blues.io/accelerators/?category=&feature=templated-notefiles)."
}
]
}
30 changes: 26 additions & 4 deletions note.template.rsp.notecard.api.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,34 @@
"$id": "https://raw.githubusercontent.com/blues/notecard-schema/master/note.template.rsp.notecard.api.json",
"title": "note.template Response Application Programming Interface (API) Schema",
"type": "object",
"version": "0.2.0",
"apiVersion": "9.1.1",
"properties": {
"success": {
"description": "Whether the template was set successfully",
"bytes": {
"description": "The number of bytes that will be transmitted to Notehub, per Note, before compression.",
"type": "integer"
},
"template": {
"description": "`true` if an active template exists on the Notefile.",
"type": "boolean"
},
"body": {
"description": "If the `verify` argument is provided and the Notefile has an active template, the template `body`.",
"type": "object"
},
"length": {
"description": "If the `verify` argument is provided and the Notefile has an active template with a payload, the payload length.",
"type": "integer"
},
"format": {
"description": "If the `format` argument is provided, this represents the format applied to the template.",
"type": "string"
}
},
"version": "0.1.1",
"apiVersion": "9.1.1"
"samples": [
{
"title": "Example Response",
"json": "{\"bytes\":40}"
}
]
}
370 changes: 370 additions & 0 deletions page.html

Large diffs are not rendered by default.

238 changes: 238 additions & 0 deletions tests/test_note_template_req.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
import pytest
import jsonschema

SCHEMA_FILE = "note.template.req.notecard.api.json"

def test_valid_req(schema):
"""Tests a minimal valid request."""
instance = {
"req": "note.template"
}
jsonschema.validate(instance=instance, schema=schema)

def test_valid_cmd(schema):
"""Tests a minimal valid command."""
instance = {
"cmd": "note.template"
}
jsonschema.validate(instance=instance, schema=schema)

def test_invalid_missing_req_cmd(schema):
"""Tests invalid request missing both req and cmd."""
instance = {
"file": "test.qo"
}
with pytest.raises(jsonschema.ValidationError) as excinfo:
jsonschema.validate(instance=instance, schema=schema)
# Should fail because neither 'req' nor 'cmd' is present

def test_valid_with_file(schema):
"""Tests valid request with file parameter."""
instance = {
"req": "note.template",
"file": "data.qo"
}
jsonschema.validate(instance=instance, schema=schema)

def test_valid_with_format(schema):
"""Tests valid request with format parameter."""
instance = {
"req": "note.template",
"format": "compact"
}
jsonschema.validate(instance=instance, schema=schema)

def test_valid_with_body(schema):
"""Tests valid request with body parameter."""
instance = {
"req": "note.template",
"body": {
"readings": {
"temp": 0,
"humid": 0
}
}
}
jsonschema.validate(instance=instance, schema=schema)

def test_valid_complete_request(schema):
"""Tests valid request with all parameters."""
instance = {
"req": "note.template",
"file": "sensors.qo",
"format": "compact",
"body": {
"temperature": 0.0,
"humidity": 0.0,
"pressure": 0.0
},
"length": 100
}
jsonschema.validate(instance=instance, schema=schema)

def test_invalid_file_type(schema):
"""Tests invalid request with non-string file parameter."""
instance = {
"req": "note.template",
"file": 123
}
with pytest.raises(jsonschema.ValidationError) as excinfo:
jsonschema.validate(instance=instance, schema=schema)
assert "123 is not of type 'string'" in str(excinfo.value)

def test_invalid_format_type(schema):
"""Tests invalid request with non-string format parameter."""
instance = {
"req": "note.template",
"format": True
}
with pytest.raises(jsonschema.ValidationError) as excinfo:
jsonschema.validate(instance=instance, schema=schema)
assert "True is not of type 'string'" in str(excinfo.value)

def test_invalid_body_type(schema):
"""Tests invalid request with non-object body parameter."""
instance = {
"req": "note.template",
"body": []
}
with pytest.raises(jsonschema.ValidationError) as excinfo:
jsonschema.validate(instance=instance, schema=schema)
assert "[] is not of type 'object'" in str(excinfo.value)

def test_cmd_instead_of_req(schema):
"""Tests valid command instead of request."""
instance = {
"cmd": "note.template",
"file": "data.qo",
"body": {"type": "sensor"}
}
jsonschema.validate(instance=instance, schema=schema)

def test_invalid_additional_properties(schema):
"""Tests that additional properties are not allowed."""
instance = {
"req": "note.template",
"invalid_property": "should_fail"
}
with pytest.raises(jsonschema.ValidationError) as excinfo:
jsonschema.validate(instance=instance, schema=schema)
assert "Additional properties are not allowed" in str(excinfo.value)

def test_valid_verify_parameter(schema):
"""Tests valid request with verify parameter."""
instance = {
"req": "note.template",
"verify": True
}
jsonschema.validate(instance=instance, schema=schema)

def test_invalid_verify_type(schema):
"""Tests invalid request with non-boolean verify parameter."""
instance = {
"req": "note.template",
"verify": "true"
}
with pytest.raises(jsonschema.ValidationError) as excinfo:
jsonschema.validate(instance=instance, schema=schema)
assert "'true' is not of type 'boolean'" in str(excinfo.value)

def test_valid_length_parameter(schema):
"""Tests valid request with length parameter."""
instance = {
"req": "note.template",
"length": 250
}
jsonschema.validate(instance=instance, schema=schema)

def test_invalid_length_type(schema):
"""Tests invalid request with non-integer length parameter."""
instance = {
"req": "note.template",
"length": "250"
}
with pytest.raises(jsonschema.ValidationError) as excinfo:
jsonschema.validate(instance=instance, schema=schema)
assert "'250' is not of type 'integer'" in str(excinfo.value)

def test_valid_length_negative_one(schema):
"""Tests valid request with length=-1 (now allowed in schema)."""
instance = {
"req": "note.template",
"length": -1
}
jsonschema.validate(instance=instance, schema=schema)

def test_valid_delete_parameter(schema):
"""Tests valid request with delete parameter."""
instance = {
"req": "note.template",
"delete": True
}
jsonschema.validate(instance=instance, schema=schema)

def test_invalid_delete_type(schema):
"""Tests invalid request with non-boolean delete parameter."""
instance = {
"req": "note.template",
"delete": 1
}
with pytest.raises(jsonschema.ValidationError) as excinfo:
jsonschema.validate(instance=instance, schema=schema)
assert "1 is not of type 'boolean'" in str(excinfo.value)

def test_valid_format_compact(schema):
"""Tests valid request with format=compact parameter."""
instance = {
"req": "note.template",
"format": "compact"
}
jsonschema.validate(instance=instance, schema=schema)

def test_invalid_length_too_negative(schema):
"""Tests invalid request with length less than -1."""
instance = {
"req": "note.template",
"length": -2
}
with pytest.raises(jsonschema.ValidationError) as excinfo:
jsonschema.validate(instance=instance, schema=schema)
assert "-2 is less than the minimum of -1" in str(excinfo.value)

def test_valid_port_parameter(schema):
"""Tests valid request with port parameter."""
instance = {
"req": "note.template",
"port": 50
}
jsonschema.validate(instance=instance, schema=schema)

def test_invalid_port_type(schema):
"""Tests invalid request with non-integer port parameter."""
instance = {
"req": "note.template",
"port": "50"
}
with pytest.raises(jsonschema.ValidationError) as excinfo:
jsonschema.validate(instance=instance, schema=schema)
assert "'50' is not of type 'integer'" in str(excinfo.value)

def test_invalid_port_too_low(schema):
"""Tests invalid request with port below minimum."""
instance = {
"req": "note.template",
"port": 0
}
with pytest.raises(jsonschema.ValidationError) as excinfo:
jsonschema.validate(instance=instance, schema=schema)
assert "0 is less than the minimum of 1" in str(excinfo.value)

def test_invalid_port_too_high(schema):
"""Tests invalid request with port above maximum."""
instance = {
"req": "note.template",
"port": 101
}
with pytest.raises(jsonschema.ValidationError) as excinfo:
jsonschema.validate(instance=instance, schema=schema)
assert "101 is greater than the maximum of 100" in str(excinfo.value)
Loading