How to exclude module name in leaf value of Identityref during ODL validation? - opendaylight

I have YANG model and JSON object which will be verified by ODL (see bellow).
I need to exclude module name from JSON to verify.
When I exclude module name from identityref ("type": "center-car-sale-type:sedan") and send only identityref name ("type": "sedan") ODL throw exception that this identityref is not found.
I want to send object without module name because "module name" + "identityref name" leads to mix metadata and instance.
How can I set up ODL validation to avoid module name in leaf value for identityref?
I parse JSON by JsonParserStream.parse(JsonReader) from org.opendaylight.yangtools.yang.data.codec.gson module.
Thank you in advance!
YANG model:
identity car-type {
description
"Car type.";
}
identity sedan {
base car-type;
}
identity minivan {
base car-type;
}
grouping car {
uses main-properties;
leaf type {
type identityref {
base car-type;
}
}
leaf max-speed {
type string;
}
}
grouping main-properties {
leaf id {
type string;
}
leaf name {
type string;
}
}
list car {
uses car;
key "id";
}
JSON:
{
"car": [
{
"id": "1",
"name": "Toyota",
"description": "Toyota car",
"type": "center-car-sale-type:sedan",
"max-speed": "300"
},
{
"id": "2",
"name": "Honda",
"description": "Honda car",
"type": "center-car-sale-type:minivan",
"max-speed": "250"
}
]
}

Related

How to return complex object as scalar type in GraphQL?

Let's imagine we have GraphQL API that can return an object Entity with Id and Name properties and I requested Name only:
query {
entities {
name
}
}
And it returns
{
"data": {
"entities": [
{
"name": "Name1"
},
{
"name": "Name2"
}
]
}
}
But what if I want to have only the name of entities as a scalar type? In other words, I want to have something like:
{
"data": {
"entities": [
"Name1",
"Name2"
]
}
}
Is it possible to have such result without changes on the GraphQL API side? Aliases, Fragments, etc. GraphQL has a lot of built-in query capabilities, but none of the known me can return complex objects as scalar type.
what you're asking for is almost impossible if you don't want to change the type definition for Entities.
This: 👇🏽
Entity: id: int! name: String
entities(): [Entity]
returns an array of objects with keys name and id.
To achieve what you're asking you either change Entity to be just a string or have your client reduce that object to an array of just Entity names when they receive it.
They could do something like this:
const data = {
entities: [
{
name: 'Name1',
},
{
name: 'Name2',
},
],
};
const entityNames = data.entities.reduce(
(acc, curr) => [...acc, curr.name],
[]
);
console.log(entityNames);

How to adapt query to API?

I'm trying to wrap my head around GraphQL.
Right now I'm just playing with the public API of Artsy (an art website, playground at https://metaphysics-production.artsy.net). What I want to achieve is following:
I want to get all node types entities without declaring them by hand (is there a shortcut for this)?
I want every node with a field type from which I can read the type, without parsing through imageUrl etc. to fint that out.
What I constructed as of right now is this:
{
search(query: "Berlin", first: 100, page: 1, entities: [ARTIST, ARTWORK, ARTICLE]) {
edges {
node {
displayLabel
imageUrl
href
}
}
}}
Very primitive I guess. Can you guys help me?
TL;DR:
1) There is no shortcut, it's not something GraphQL offers out of the box. Nor is it something I was able to find via their Schema.
2) Their returned node of type Searchable does not contain a property for type that you're looking for. But you can access it via the ... on SearchableItem (union) syntax.
Explanation:
For question 1):
Looking at their schema, you can see that their search query has the following type details:
search(
query: String!
entities: [SearchEntity]
mode: SearchMode
aggregations: [SearchAggregation]
page: Int
after: String
first: Int
before: String
last: Int
): SearchableConnection
The query accepts an entities property of type SearchEntity which looks like this:
enum SearchEntity {
ARTIST
ARTWORK
ARTICLE
CITY
COLLECTION
FAIR
FEATURE
GALLERY
GENE
INSTITUTION
PROFILE
SALE
SHOW
TAG
}
Depending on what your usecase is, if you're constructing this query via some code, then you can find out which SearchEntity values they have:
{
__type(name: "SearchEntity") {
name
enumValues {
name
}
}
}
Which returns:
{
"data": {
"__type": {
"name": "SearchEntity",
"enumValues": [
{
"name": "ARTIST"
},
{
"name": "ARTWORK"
},
...
}
}
}
then store them in an array, omit the quotation marks from the enum and pass the array back to the original query directly as an argument.
Something along the lines of this:
query search($entities: [SearchEntity]) {
search(query: "Berlin", first: 100, page: 1, entities: $entities) {
edges {
node {
displayLabel
imageUrl
href
}
}
}
}
and in your query variables section, you just need to add:
{
"entities": [ARTIST, ARTWORK, ...]
}
As for question 2)
The query itself returns a SearchableConnection object.
type SearchableConnection {
pageInfo: PageInfo!
edges: [SearchableEdge]
pageCursors: PageCursors
totalCount: Int
aggregations: [SearchAggregationResults]
}
Digging deeper, we can see that they have edges, of type SearchableEdge - which is what you're querying.
type SearchableEdge {
node: Searchable
cursor: String!
}
and finally, node of type Searchable which contains the data you're trying to access.
Now, the type Searchable doesn't contain type:
type Searchable {
displayLabel: String
imageUrl: String
href: String
}
But, if you look at where that Searchable type is implemented, you can see SearchableItem - which contains the property of displayType - which doesn't actually exist in Searchable.
You can access the property of SearchableItem and get the displayType, like so:
{
search(query: "Berlin", first: 100, page: 1, entities: [ARTIST, ARTWORK, ARTICLE]) {
edges {
node {
displayLabel
imageUrl
href
... on SearchableItem {
displayType
}
}
}
}
}
and your result will look like this:
{
"data": {
"search": {
"edges": [
{
"node": {
"displayLabel": "Boris Berlin",
"imageUrl": "https://d32dm0rphc51dk.cloudfront.net/CRxSPNyhHKDIonwLKIVmIA/square.jpg",
"href": "/artist/boris-berlin",
"displayType": "Artist"
}
},
...

Problem to structure property of an object using [apollo / graphql]

Problem
Hello friends,
I am working on an api using Apollo Server.
I am having the problem of how to display the nextEpisodeDate property only once. My solution shows nextEpisodeDate in all sub-array in the episodes property and it shouldn't be like that.
I hope someone can help me !
JSON Example
"episodes": [
{
"nextEpisodeDate": "2020-01-17"
},
{
"episode": 3,
"id": "53789/dorohedoro-3",
"imagePreview": "https://cdn.animeflv.net/screenshots/3274/3/th_3.jpg"
},
{
"episode": 2,
"id": "53755/dorohedoro-2",
"imagePreview": "https://cdn.animeflv.net/screenshots/3274/2/th_3.jpg"
},
{
"episode": 1,
"id": "53705/dorohedoro-1",
"imagePreview": "https://cdn.animeflv.net/screenshots/3274/1/th_3.jpg"
}
]
typeDefs
const resolvers = require('./resolvers');
const {gql} = require('apollo-server');
const typeDefs = gql `
extend type Query{
latest_anime: [Animes]
}
type Animes{
title: String
poster: String
synopsis: String
debut: String
type: String
rating: String
genres: [String]
episodes: [Episodes]
}
type Episodes{
nextEpisodeDate: String
episode: String
id: String
imagePreview: String
}
`
module.exports = {
typeDefs,
resolvers
};
Apollo Playground
query{
latest_anime{
title
poster
synopsis
debut
type
rating
genres
episodes{
nextEpisodeDate
episode
id
imagePreview
}
}
}
Apollo Playground Output
{
"data": {
"latest_anime": [
{
"title": "Tsugumomo OVA",
"poster": "https://animeflv.net/uploads/animes/covers/3275.jpg",
"synopsis": "OVA 4.6Kazuya Kagami nunca va a ningún lado sin su preciada “Sakura Obi” que su madre le regaló. Un día, una hermosa chica vestida con un kimono llamada Kiriha aparece ante él. Naturalmente, ella comienza a vivir en su habitación. ¿Naturalmente? ¡Esto solo es el inicio de la embarazosa y confusa...",
"debut": null,
"type": "OVA",
"rating": "4.6",
"genres": [
"accion",
"comedia",
"ecchi",
"escolares",
"seinen",
"sobrenatural"
],
"episodes": [
{
"nextEpisodeDate": null,
"episode": null,
"id": null,
"imagePreview": null
},
{
"nextEpisodeDate": null,
"episode": "1",
"id": "53753/tsugumomo-ova-1",
"imagePreview": "https://cdn.animeflv.net/screenshots/3275/1/th_3.jpg"
}
]
},
]
}
}
The only way you can get the desired response structure is to have two separate types. A field must have exactly one type, but you can use an abstract type like a union or interface in order to have each individual item in the list resolve to one of multiple types at runtime.
type AiredEpisode implements Episode {
id: String
episode: String
imagePreview: String
}
type UpcomingEpisode implements Episode {
id: String
nextEpisodeDate: String
}
interface Episode {
id: String
}
type Anime {
episodes: [Episode]
# other fields
}
You would then query the episodes like this:
query {
latest_anime {
episodes {
# fields on the interface itself like id are common to all
# implementing types so they don't need to be inside a fragment
id
# fields specific to one of the types need to be inside a fragment
... on UpcomingEpisode {
nextEpisodeDate
}
... on AiredEpisode {
id
episode
imagePreview
}
}
}
}
Side note: if your API doesn't return an id for the upcoming episodes, you should still provide one (you could use the show's id, for example, you just want to make sure it's unique). This will ensure that you don't run into caching issues if you use a client like Apollo on the front end.

Aws AppSync Query erring out while using a resolver

Im new to AWS AppSync however its been pretty easy to learn and understand.
Im trying to create a resolver that when the user runs getChore(id: "") it will return all the chore information. Which its successfully doing, the problem is within the chore there are two fields: createdBy & assignedTo which are linked to a user type.
type Chore {
id: ID!
title: String
desc: String
status: String
reward: Float
retryDeduction: Float
required: Boolean
createdDate: AWSDateTime
date: AWSDateTime
interval: String
assignedTo: User
createdBy: User
}
type User {
id: ID!
age: Int
f_name: String
l_name: String
type: Int
admin: Boolean
family: Family
}
within aws appsync in trying to attach a resolver to assignedTo: User and createdBy: User so my query will look like:
query getChore {
getChore(id: "36d597c8-2c7e-4f63-93ee-38e5aa8f1d5b") {
id
...
...
assignedTo {
id
f_name
l_name
}
createdBy {
id
f_name
l_name
}
}
}
however when i fire off this query im getting an error:
The provided key element does not match the schema (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException;
which i have researched and cant seem to find the correct soltuion.
The resolver im using is:
{
"version": "2017-02-28",
"operation": "GetItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($ctx.args.id),
}
}
return:
$util.toJson($ctx.result)
When you get the The provided key element does not match the schema error, it's because your request mapping template key doesn't match the primary key in DynamoDB. You can enable CloudWatch Logs in your Application settings to see exactly what was sent to DynamoDB.
I'm not able to know what's wrong with your template because your sample lacks some information, if you can answers the questions pertaining to your application:
- Where are the users stored? Are they stored in their own DDB table separate from the chores, and is the hash key on the users table id as well?
- In the chores table how do you know which user your chore is assignedTo or createdBy? Is there a user id stored on the chore DDB item?
- Is the request mapping template you posted corresponding to the resolver attached to Chore.assignedTo? If yes, using $ctx.args.id will actually do a GetItem based on the chore id not the user it's assigned to.
Finally, I reproduced your application and I was able to make it work with a few changes.
Prerequisites:
I have a chores and a users DynamoDB table with both having id as hash key. These two tables are mapped as datasources in AppSync.
I have one chore in the chores tables that looks like
{
"assignedTo": "1",
"createdBy": "2",
"id": "36d597c8-2c7e-4f63-93ee-38e5aa8f1d5b",
"title": "Chore1"
}
and two users in the users table:
{
"f_name": "Alice",
"id": "2",
"l_name": "Wonderland"
}
and
{
"f_name": "John",
"id": "1",
"l_name": "McCain"
}
I used your GraphQL schema
Resolvers
Resolver on Query.getChore pointing to the chores table:
{
"version": "2017-02-28",
"operation": "GetItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($ctx.args.id),
}
}
Resolver on Chore.assignedTo pointing to the users table (note the $ctx.source.assignedTo instead of $ctx.args)
{
"version": "2017-02-28",
"operation": "GetItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($ctx.source.assignedTo),
}
}
Similarly, resolver on Chore.createdBy pointing to the users table:
{
"version": "2017-02-28",
"operation": "GetItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($ctx.source.createdBy),
}
}
All resolvers response mapping template use the pass-through.
Running the query
Finally, when running your query:
query getChore {
getChore(id: "36d597c8-2c7e-4f63-93ee-38e5aa8f1d5b") {
id
assignedTo {
id
f_name
l_name
}
createdBy {
id
f_name
l_name
}
}
}
I get the following results:
{
"data": {
"getChore": {
"id": "36d597c8-2c7e-4f63-93ee-38e5aa8f1d5b",
"assignedTo": {
"id": "1",
"f_name": "John",
"l_name": "McCain"
},
"createdBy": {
"id": "2",
"f_name": "Alice",
"l_name": "Wonderland"
}
}
}
}
Hope it helps!

Can I add data to a GraphQL edge?

I'm playing around with GraphQL, and I've run across the concept of connections and edges.
From what I understand it's not uncommon to see metadata on the connection, like the totalCount property in the following snippet.
type UserFriendsConnection {
pageInfo: PageInfo!
edges: [UserFriendsEdge]
totalCount: Int
}
My questions is whether it's OK to put arbitrary metadata on the edge also, and if the following would be a decent way to do that.
I felt like a query and a response would best illustrate what I'm looking for. It's the role property I want to place somewhere that makes sense.
I feel like it doesn't belong in the User type since the role describes the type of connection/relationship the User has with a Group.
# Query
{
me {
id
name
groupsConnection {
edges {
node {
id
name
membersConnection {
edges {
node {
id
name
}
role <--- HERE
}
}
}
role <--- HERE
}
}
}
}
# Response
{
"data": {
"me": {
"id": "1Edj3hZFg",
"name": "John Doe",
"groupsConnection": {
"edges": [
{
"node": {
"id": "bpQgdZweQE",
"name": "Fishing Team",
"membersConnection": {
"edges": [
{
"node": {
"id": "1Edj3hZFg",
"name": "John Doe"
},
"role": "ADMINISTRATOR" <--- HERE
},
{
"node": {
"id": "7dj37dH2d",
"name": "Rebecca Anderson"
},
"role": "MEMBER" <--- HERE
}
]
}
},
"role": "ADMINISTRATOR" <--- HERE
}
]
}
}
}
}
Connections are part of the Relay specification. Relay itself is a GraphQL client, although you can have a Relay-compliant GraphQL server without actually using Relay on the front end. According to the spec:
Edge types must have fields named node and cursor. They may have additional fields related to the edge, as the schema designer sees fit.
It's common enough to see additional fields on these types and it certainly makes sense. One word of caution though. If we have a User type, we may create a UserConnection and a UserEdge:
type UserConnection {
pageInfo: PageInfo!
egdes: [UserEdge!]!
}
type UserEdge {
cursor: String!
edge: User!
}
We can then use that connection type in all sorts of places...
type Query {
allUsers: UserConnection!
# other fields
}
type Group {
members: UserConnection!
# other fields
}
type User {
coworkers: UserConnection!
# other fields
}
However, if you add a field like role to UserEdge, that field will only make sense in the context of the members field on the Group type. It would have to return null or some dummy value in all other contexts, which can introduce unnecessary confusion.
That means, if you're going to introduce extra fields on your edge type that are relationship-dependent, you should probably create connection and edge types that are specific to that relationship:
type GroupUserConnection {
pageInfo: PageInfo!
egdes: [GroupUserEdge!]!
}
type GroupUserEdge {
cursor: String!
edge: User!
role: Role!
}
This way, you can still use a regular UserConnection for other fields and avoid clients unnecessarily requesting a role where there isn't one.

Resources