Scopes
A scope is a place in a hierarchy, written onto the entity itself, such as /Madrid/Gardens, /CompanyA/OrganizationB/UnitC or /North/Plant/Floor3. You choose what the hierarchy means. Entities stamped with scopes can then be selected with one query parameter, scopeQ, without a q term and without a property reserved for the purpose.
The rules of the format and the wildcard patterns are in Scopes. This guide shows how to use the two halves together, stamping and filtering.
Stamping a scope
Write one scope, or an array of them, in the entity's scope member.
- http
- curl
- rust
- python
POST /ngsi-ld/v1/entities
Content-Type: application/json
{
"id": "urn:ngsi-ld:Building:1",
"type": "Building",
"scope": [
"/Madrid/Gardens/ParqueNorte",
"/CompanyA/OrganizationB/UnitC"
]
}
curl -i http://localhost:1056/ngsi-ld/v1/entities \
-H 'Content-Type: application/json' \
-d '{
"id": "urn:ngsi-ld:Building:1",
"type": "Building",
"scope": [
"/Madrid/Gardens/ParqueNorte",
"/CompanyA/OrganizationB/UnitC"
]
}'
let building = Entity::builder()
.id(iri!("urn:ngsi-ld:Building:1"))
.entity_type("Building")
.scope(["/Madrid/Gardens/ParqueNorte", "/CompanyA/OrganizationB/UnitC"])
.build()?;
let outcome = client.entities().create(&building).send().await?;
response = requests.post(
f"{BASE}/entities",
json={
"id": "urn:ngsi-ld:Building:1",
"type": "Building",
"scope": [
"/Madrid/Gardens/ParqueNorte",
"/CompanyA/OrganizationB/UnitC",
],
},
)
assert response.status_code == 201
HTTP/1.1 201 Created
A scope is levels separated by /, each level starting with a letter and continuing with letters, digits or _. The leading / is optional and is kept as you wrote it, so /Madrid and Madrid are two different strings on the entity, and both are legal. An entity can carry scopes from unrelated hierarchies at once, which is how one building sits in both a geography and an organisation chart.
To remove a scope, send the NGSI-LD Null, urn:ngsi-ld:null, as the value of scope in a partial update or a merge.
Filtering with scopeQ
scopeQ selects the entities whose scope matches a pattern. Without wildcards, it matches exactly that scope, and nothing below it.
- http
- curl
- rust
- python
GET /ngsi-ld/v1/entities?type=Building&scopeQ=/Madrid/Gardens/ParqueNorte
Accept: application/json
curl -i 'http://localhost:1056/ngsi-ld/v1/entities?type=Building&scopeQ=/Madrid/Gardens/ParqueNorte' \
-H 'Accept: application/json'
let page = client
.entities()
.query()
.entity_type("Building")
.scope_q(scope_query::scope("/Madrid/Gardens/ParqueNorte"))
.send()
.await?;
response = requests.get(
f"{BASE}/entities",
params={
"type": "Building",
"scopeQ": "/Madrid/Gardens/ParqueNorte",
},
headers={"Accept": "application/json"},
)
assert response.status_code == 200
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"id": "urn:ngsi-ld:Building:1",
"type": "Building",
"scope": [
"/Madrid/Gardens/ParqueNorte",
"/CompanyA/OrganizationB/UnitC"
]
}
]
A building stamped only /Madrid/Gardens is not answered by that query, because matching a scope exactly means the whole path, and reaching descendants takes a wildcard. + stands for exactly one level below, and a trailing # covers the level before it and everything underneath, so scopeQ=/Madrid/# matches /Madrid itself and every path under it. In a URL the # must be percent-encoded as %23, or the client cuts the query short. In Rust, scope_query::subtree("/Madrid") writes that pattern for you.
Matching looks at levels, not spellings, and the pattern and the stored scope each work with or without the leading slash.
Combining scope tests
Several scopes can be required or offered. A disjunction selects entities in any of the scopes, written with , or |.
- http
- curl
- rust
- python
GET /ngsi-ld/v1/entities?type=Area&scopeQ=/Root/AreaA|/Root/AreaB
curl -i 'http://localhost:1056/ngsi-ld/v1/entities?type=Area&scopeQ=/Root/AreaA|/Root/AreaB'
let page = client
.entities()
.query()
.entity_type("Area")
.scope_q(scope_query::scope("/Root/AreaA").or("/Root/AreaB"))
.send()
.await?;
response = requests.get(
f"{BASE}/entities",
params={
"type": "Area",
"scopeQ": "/Root/AreaA|/Root/AreaB",
},
)
assert response.status_code == 200
A conjunction selects entities holding every scope named, and the grammar admits it only inside parentheses.
- http
- curl
- rust
- python
GET /ngsi-ld/v1/entities?type=Building&scopeQ=(/Madrid/Gardens/ParqueNorte;/CompanyA/OrganizationB/UnitC)
curl -i 'http://localhost:1056/ngsi-ld/v1/entities?type=Building&scopeQ=(/Madrid/Gardens/ParqueNorte;/CompanyA/OrganizationB/UnitC)'
let page = client
.entities()
.query()
.entity_type("Building")
.scope_q(scope_query::all_of([
"/Madrid/Gardens/ParqueNorte",
"/CompanyA/OrganizationB/UnitC",
]))
.send()
.await?;
response = requests.get(
f"{BASE}/entities",
params={
"type": "Building",
"scopeQ": "(/Madrid/Gardens/ParqueNorte;/CompanyA/OrganizationB/UnitC)",
},
)
assert response.status_code == 200
Only the building stamped with both is answered. A group can be joined to a disjunction, scopeQ=(/Aa;/Bb)|/Cc, but a ; outside parentheses refuses the whole query with 400 BadRequestData, as does a group nested inside a group. Write the conjunction first, wrap it once, then disjoin.
Where scopeQ runs
scopeQ is a query parameter of the entity query, on the URL and in the POST /ngsi-ld/v1/entityOperations/query body alike. It composes with type, q, the geoquery parameters and pagination. The scope narrows the candidate entities first, and the other tests apply to what remains.
What gets refused
400BadRequestDatafor a scope whose levels break the format, such as/a-b,/1stor a trailing/.400BadRequestDatafor ascopeQwith an unparenthesised conjunction, or a group inside a group.- An entity without a
scopemember never matches ascopeQ, whatever the pattern.