YANG - Modeling non-mandatory containers - ietf-netmod-yang

Currently I am working with YANG as part of a (legacy) Python project.
I am somewhat stuck at the task of defining a schema, which shall then be used to verify data, organized as a Python dictionary.
If it is possible, I would "like" to keep the current structure, since a lot of the codebase is using this data.
An "unaltered" piece of data:
"namespace": { # Mandatory
"management": { # Optional
"interfaces": { # Mandatory
"m0": { # Optional
"leaf1": "..."
}
}
},
"benchmark": { # Optional
"interfaces": { # Mandatory
"b0": { # Optional
"leaf1": "...",
"leaf2": "..."
},
"b1": { # Optional
"leaf1": "...",
"leaf2": "..."
}
}
}
}
My problem is that everything marked as "optional" (in the example) would be modeled as a container but it seems that they cannot be defined as optional (i.e.: mandatory false;) according to RFC6020.
Therefore, I defined a model that is using lists. Meaning some nodes of the Python Dict (management, benchmark, m0, b0, b1) are now list elements and cannot be accessed in the current fashion, e.g.: data['namespace']['management']...
The modified example looks like this:
"namespace": [
{
"desc": "management",
"interfaces": [
{
"leaf1": "..."
}
]
},
{
"desc": "benchmark",
"interfaces": [
{
"leaf1": "...",
"leaf2": "..."
},
{
"leaf1": "...",
"leaf2": "..."
}
]
}
]
The describing (snippet from my current) YANG model:
list namespace {
description "Namespace definitions.";
key desc;
leaf desc { type string; }
uses leaf-definitions;
list interfaces {
key leaf1;
uses leaf-definitions;
}
}
The verification is successful and the conversion of the data (itself) is not a problem, but it is resulting in a big pile of broken code.
This leads to my question(s):
Am I correct - are containers in YANG always mandatory?
Is there maybe another way to model this scenario? (Without breaking "too much")
I am very thankful for your input, since I am rather new to YANG!

Am I correct - are containers in YANG always mandatory?
Quite the contrary. They are always optional, unless they contain mandatory nodes (mandatory leaf, a list or leaf-list with min-elements > 0, etc.). In other words, containers inherit this property from their descendants. This of course only applies to non-presence containers. A presence container with mandatory children does not inherit this property, since that would defeat its purpose (presence). You probably missed the definition of a mandatory node in RFC6020:
A mandatory node is one of:
o A leaf, choice, or anyxml node with a "mandatory" statement with
the value "true".
o A list or leaf-list node with a "min-elements" statement with a
value greater than zero.
o A container node without a "presence" statement, which has at
least one mandatory node as a child.
This should already be helpful for your second question.
Is there maybe another way to model this scenario? (Without breaking "too much")
Abuse presence containers. They are always optional. You could also probably avoid using the lists by introducing some mandatory children to a non-presence container. Based on your initial data:
module mandatory-optional-branch {
namespace "org:example:mandatory-optional-branch";
prefix "mob";
grouping leafs {
leaf leaf1 {type string;}
leaf leaf2 {type string;}
}
list namespace { // mandatory
config false;
min-elements 1;
max-elements 1;
container management { // optional by nature
presence "I have mandatory children, but am not mandatory. Yay for me.
Of course my presence should have some meaning.";
list interfaces { // mandatory
min-elements 1;
max-elements 1;
container m0 { // optional - no mandatory node children
leaf leaf1 {type string;}
}
}
}
container benchmark { // optional by nature
presence "Same as 'management' above.";
list interfaces { // mandatory
min-elements 1;
max-elements 1;
container b0 { // optional - no mandatory node children
uses leafs;
}
container b1 { // optional - no mandatory node children
uses leafs;
}
}
}
}
}

Related

unique constraints for Yang leaf-list across all nodes in a list

I have below yang models
container PORT {
description "PORT part of config_db.json";
list PORT_LIST {
key "name";
leaf name {
type string {
length 1..128;
}
}
leaf-list lanes {
type string {
length 1..128;
}
}
}
}
And below config
PORT": {
"PORT_LIST": [
{
"name": "Ethernet8",
"lanes": ["65", "66"]
},
{
"name": "Ethernet9",
"lanes": ["65", "67"]
}
]
}
How to add a constraint, 'must' or 'unique' such that elements of leaf-list 'lanes' are unique across all nodes in PORT_LIST. In above example value '65' in 'lanes' field should be allowed only in one node.
The unique statement may only refer to one or more leaf statements, so that is not an option.
You should be able to achieve a similar result with a must statement and a condition like this:
module c {
yang-version 1.1;
namespace "c:uri";
prefix "c";
container PORT {
description "PORT part of config_db.json";
list PORT_LIST {
key "name";
must "count(lanes[current()/preceding-sibling::PORT_LIST/lanes = .]) = 0" {
error-message "Lanes entries must be unique accross all entries of PORT_LIST";
}
leaf name {
type string {
length 1..128;
}
}
leaf-list lanes {
type string {
length 1..128;
}
}
}
}
}
The condition says something along the lines of: if there are any lanes for this PORT_LIST entry, none of them should have the same value as the lanes in any of the PORT_LIST entries that come before this one.
<?xml version="1.0" encoding="utf-8"?>
<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<PORT xmlns="c:uri">
<PORT_LIST>
<name>Ethernet8</name>
<lanes>65</lanes>
<lanes>66</lanes>
</PORT_LIST>
<PORT_LIST>
<name>Ethernet9</name>
<lanes>65</lanes>
<lanes>67</lanes>
</PORT_LIST>
</PORT>
</config>
Error at (9:5): failed assert at "/nc:config/c:PORT/c:PORT_LIST": Lanes entries must be unique accross all entries of PORT_LIST
This is just a quick example, there may be more efficient ways to define the condition.

Comparing two objects in Joi validation (eg. to avoid duplicates)

I'm using Joi to validate a complex form entry. The form asks for two addresses, mainContactAddress and seniorContactAddress. I want to validate them to ensure they aren't the same address.
Each address is an object like this:
{
"line1": "123 Some Street",
"line2": "Some Town",
"county": "Some County",
"postcode": "123 ABC",
"townCity": "City"
}
I initially tried this:
Joi.ukAddress().invalid(Joi.ref('seniorContactAddress'))
(ukAddress() is a custom extension I've created which specifies each of the above fields as a required string.)
This doesn't work, because the equality === comparison between the two objects returns false even when they have the same string values.
I can't see a Joi method to do this. I was hoping to be able to serialise the object (eg. something like Object.values(mainContactAddress).join(',') and then compare the resulting strings) but Joi.ref() only gives, well, a reference to the object, so I can't call functions against it directly.
Any thoughts on how I could achieve this validation/comparison?
I ended up writing a custom rule for my extension:
{
// Enforce a unique address compared to the senior contact
name: 'mainContact',
validate(params, value, state, options) {
// Format addresses into a comparable string,
// making sure we sort them as the stored version
// is in a different order to the form-submitted one.
const serialize = address =>
Object.values(address)
.sort()
.join(',');
const seniorContactAddress = get(
state.parent,
'seniorContactAddress',
[]
);
if (serialize(seniorContactAddress) === serialize(value)) {
return this.createError(
'address.matchesSenior',
{ v: value },
state,
options
);
} else {
return value;
}
}
}
This does feel like an anti-pattern (eg. abusing state to look at other values in the Joi object) but it does what I needed.

must have a selection of subfields. Did you mean \"createEvent { ... }\"?", [graphql] [duplicate]

Hi I am trying to learn GraphQL language. I have below snippet of code.
// Welcome to Launchpad!
// Log in to edit and save pads, run queries in GraphiQL on the right.
// Click "Download" above to get a zip with a standalone Node.js server.
// See docs and examples at https://github.com/apollographql/awesome-launchpad
// graphql-tools combines a schema string with resolvers.
import { makeExecutableSchema } from 'graphql-tools';
// Construct a schema, using GraphQL schema language
const typeDefs = `
type User {
name: String!
age: Int!
}
type Query {
me: User
}
`;
const user = { name: 'Williams', age: 26};
// Provide resolver functions for your schema fields
const resolvers = {
Query: {
me: (root, args, context) => {
return user;
},
},
};
// Required: Export the GraphQL.js schema object as "schema"
export const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
// Optional: Export a function to get context from the request. It accepts two
// parameters - headers (lowercased http headers) and secrets (secrets defined
// in secrets section). It must return an object (or a promise resolving to it).
export function context(headers, secrets) {
return {
headers,
secrets,
};
};
// Optional: Export a root value to be passed during execution
// export const rootValue = {};
// Optional: Export a root function, that returns root to be passed
// during execution, accepting headers and secrets. It can return a
// promise. rootFunction takes precedence over rootValue.
// export function rootFunction(headers, secrets) {
// return {
// headers,
// secrets,
// };
// };
Request:
{
me
}
Response:
{
"errors": [
{
"message": "Field \"me\" of type \"User\" must have a selection of subfields. Did you mean \"me { ... }\"?",
"locations": [
{
"line": 4,
"column": 3
}
]
}
]
}
Does anyone know what I am doing wrong ? How to fix it ?
From the docs:
A GraphQL object type has a name and fields, but at some point those
fields have to resolve to some concrete data. That's where the scalar
types come in: they represent the leaves of the query.
GraphQL requires that you construct your queries in a way that only returns concrete data. Each field has to ultimately resolve to one or more scalars (or enums). That means you cannot just request a field that resolves to a type without also indicating which fields of that type you want to get back.
That's what the error message you received is telling you -- you requested a User type, but you didn't tell GraphQL at least one field to get back from that type.
To fix it, just change your request to include name like this:
{
me {
name
}
}
... or age. Or both. You cannot, however, request a specific type and expect GraphQL to provide all the fields for it -- you will always have to provide a selection (one or more) of fields for that type.

Query variables in Dgraph filter

I am trying to use a variables (which is a scalar) in a #filter(ge(...)) call, but I run into an error
Given the following query
{
ua(func: uid(0xfb7f7)) {
uid
start_ua {
sua as index
}
recorded_in {
actions #filter(ge(index, sua)){
index
}
}
}
}
I get the following error
{
"errors": [
{
"code": "ErrorInvalidRequest",
"message": "Some variables are defined but not used\nDefined:[sua]\nUsed:[]\n"
}
],
"data": null
}
Now if I remove the sua as ... and the #filter(...) from the query, all works fine.
My Dgraph version is v1.0.13.
I tried replacing #filter(ge(index, sua)) with #filter(ge(index, val(sua))) but I still run into an error:
{
"errors": [
{
"code": "ErrorInvalidRequest",
"message": ": No value found for value variable \"sua\""
}
],
"data": null
}
What am I doing wrong?
Here's what the Dgraph docs say about value variables (emphasis added): https://docs.dgraph.io/query-language/#value-variables
Value variables store scalar values. Value variables are a map from the UIDs
of the enclosing block to the corresponding values.
It therefore only makes sense to use the values from a value variable in a
context that matches the same UIDs - if used in a block matching different
UIDs the value variable is undefined.
The start_ua and recorded_in are different subgraphs, which means variables defined in one are undefined in the other within the same query block.
What you can do is use multiple query blocks. Variables can be accessed across blocks:
{
block1(func: uid(0xfb7f7)) {
uid
start_ua (first: 1) {
sua as index
}
}
block2(func: uid(0xfb7f7)) {
recorded_in {
actions #filter(ge(index, val(sua))) {
index
}
}
}
}
I also added (first: 1) to the start_ua predicate, so that at most 1 node is fetched and stored the sua variable. If your data is already structured that way, then that's not needed.
val(sua) gets the value of the variable sua.

Can I parameterize labels and properties on CREATE or SET? (REST and transaction)

I have a query
1. CREATE (a:%1$s {props}), (b:%2$s {props2}), (b)-[:%3$s {relProps}]->(a)
2. MATCH (a:%1$s { value:{value} })-[:%2$s]->(b) WHERE (b:%3$s) SET (b {props})
I'm using underscore.string to allow string format but would love to just stick with parameters.
Is it possible to parameterize labels such as
{
"query": CREATE (a:{label} {props}),
"params": {
"label":"SomeLabel",
"props":{....}
}
}
and is it also possible to parameterize properties on a SET?
{
"query": "MATCH ..... SET (node {props})"
"params": {
"props":{
"prop1:":"Property Name",
....
}
}
}
Also is there a way to parameterize on a "MERGE"? it gives me 'Parameter maps cannot be used in MERGE patterns (use a literal map instead, eg. "{id: {param}.id}")'
EDIT: what about parameterizing where clause?
MATCH (:Identity%1$s {nodeId:{nodeId})-[r*2..3]-(node1)-[b:%2$s]->(node2) %4$s return *
I have %4$s there for me to put whatever clauses I need to. If I want to have it as
WHERE node1.nodeId= {someNodeId} SET b= {props}
is that possible??
Also when I'm doing a transaction SET node={props} does not seem to work. I tried
statements:[
{
"statement":"..... SET node={props}",
"parameters":{
"props": {
"description":"some description"
}
}
}
]
Any suggestions?? Thank you!
You cannot parameterize labels since the query plan might look different for a different label.
Parameterizing multiple properties using a map is possible, note the slight difference in the SET syntax:
{
"query": "MATCH ..... SET node = {props}"
"params": {
"props":{
"prop1:":"Property Name",
....
}
}
}
Not 100% about MERGE but I guess this should work:
{
"query": "MERGE (n:Label {identifier: {idValue}) ON CREATE SET n = {props}"
"params": {
"identifier": 123,
"props":{
"identifier": 123,
"prop1:":"Property Name",
....
}
}
}
I found out!
CREATE ... SET node = {props}
does the trick to set multiple properties with parameters
doc: http://docs.neo4j.org/chunked/snapshot/cypher-parameters.html