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.
Related
So i have a function that basically sorts a MAP of MAP's, but it uses different MAP properties for the sort, so i want to sort by age or by name based on the user input.
{
"name": "John",
"other_data:
{
"age": "20",
"nickname": "Doe2020"
}
}
So my idea was to make a generic function to sort this. There is some way where i can send the path of each property as a function argument to sort based on this argument ?
Like "mySortFunction(MapPath: ["other_data"]["age"]);"
or "mySortFunction(MapPath: ["name"]);"
Thanks!
You could try something like this, if your properties are at max two levels:
mySortFunction(Map map, {String firstLevel, String secondLevel}){
if(firstLevel != null){
if(secondLevel != null){
sortMap(map[firstLevel][secondLevel]); // Your sorting logic here
} else {
sortMap(map[firstLevel]); // sorting logic strikes back
}
}
}
You can add more optional variables and increase the if logic for deeper maps
I am using lighthouse-php as Api Gateway in a micro services architecture.
So for all my types I make a request internally through Guzzle.
But I am needing to implement filters that are suitable for any type and that give flexibility when making queries.
I need to implement a query like this:
query news (
order_by: {publication_date: desc}
where: {
_or: {categories_id: { _eq: 1 }, title: { _ilike: "news" } }
}
limit: 10
offset: 20
) {
id
category_name: name
photo
publication_date
text
title
}
But I have no idea how to implement this "where" filter that receives a composite object as in this example.
Remember that this query will not use any model within lumen, since it will be a custom query that will make a request to the microservice of news.
What I need is the way that my query receives whatever comes in where, limit and order, to send it on request. But I have no idea how to build something like this in the scheme.
Anyone have any idea how to do it?
Thanks friends.
Yes, you can.
Just now I'm making an component that will receive criterias to filter in graphql query so I need to fill filter's where params with those criterias.
Imagine the following schema:
type News{
id: ID!
title: String!
views: Int!
}
type Query{
getNews(where: _ #whereConditions(columns:["title", "views"])) : [News!] #all
}
We can make a query and fill where variables later
query GetNews($whereNews: [GetNewsWhereWhereConditions!]){
getNews(where: {OR: $whereNews}){
title
views
}
}
When querying we can fill the variables sending an object like
{
"where":[
{"column": "TITLE", "operator": "LIKE", "value": "Amazing title"},
{"column": "VIEWS", "operator": "GTE", "value": 10,
]
}
I made a beer matching dating app for a school assignment. Unfortunately am I having a bit of trouble finding out how to search in my database on a template injected number as object.
I've tried this I read on MongoDB docs
The database looks like this :
beerProfile: Object
↳24589: Object
↳name: "Heineken"
img: "https://untappd.akamaized.net/site/beer_logos/beer94130_52756_sm.jpeg"
description: "Heinken is a beer"
bid:"24589"
So beerProfile is an object and has the object 24589 inside it. Inside the 24589 object are name, imd, description and bid.
I tried to use the find() function. ( the collection is called users )
db.collection('users').find( { [24589]: [{name: [Heineken]}] }, { name: 1, bid: 1 }, done);
And I also tried :
db.collection('users').find( { $text: { $search: 24589 } }, done);
I would like to make it return the object values of the 24589 object. Does anyone how I can achieve this ?
I think your "schema" became unnecessarily complex by using a variable (24589) as a key. You should change it to something like this:
beerProfile: Object
↳beer: Object
↳name: "Heineken"
img: "https://untappd.akamaized.net/site/beer_logos/beer94130_52756_sm.jpeg"
description: "Heinken is a beer"
bid:"24589"
Then you can use a simple find():
db.collection('users').find( { "beer.bid": "24589"})
I have a function that performs a search. The search can be done a few different ways (by looking for an ID or by querying a few attributes). However, I want to limit what attributes can be passed in. I thought I could do something like:
interface Search {
_id?: string
people?: number
partyName?: string
otherField? string
}
function search(query: Search) {
myDbConnection.find(query).then(... // etc
}
The problem is that any object will conform to this, and query can contain extra attributes. For example, this could be passed:
search({otherField: "foo", aProtectedField: "bar"})
and aProtectedField would be passed along to find.
I am wondering if there is a typescript way of enforcing the attributes passed. Sort of strong-parameters from the Rails world. I know I can do things like pick form lodash or maybe even make a SearchObject class and use the constructor as a means of discarding the extra attributes, but I feel like there is a way to do this within Typescript that I just don't know about.
You could make all the properties required then do an assertion to pass in a subset of the properties.
For example:
interface Search {
_id: string;
people: number;
partyName: string;
otherField: string;
}
function search(query: Search) {
// code here
}
search({ people: 2 } as Search); // ok
search({ otherField: "foo", aProtectedField: "bar" }); // error, good
search({ otherField: "foo", aProtectedField: "bar" } as Search); // error, good
What version of Typescript are you on? Since Typescript 1.6 there has been improved checking for object literals.
On TS 1.8 when I try to run your code I get:
error TS2345: Argument of type '{ otherField: string; aProtectedField: string; }' is not assignable to parameter of type 'Search'.
Object literal may only specify known properties, and 'aProtectedField' does not exist in type 'Search'.
This and noImplicitAny should catch the errors you're worried about.
I want to limit what attributes can be passed in. [...] query can contain extra attributes.
The way I see it, this is controversial. If you limit what attributes a given object might contain, then it is, by definition, limited to that set of attributes, and cannot contain others not allowed by its specs.
Since there is practically nothing you can do about the any type, my recommendation is to resolve this the type-safe way, by defining an option for additional attributes:
interface Search {
_id?: string;
people?: number;
partyName?: string;
additionalFields?: { [key: string]: any };
}
search({ people: 2 }); // ok
search({ _id: "jd", people: 2 }); // ok
search({ _id: "jd", additionalFields: { otherField: "foo" } }); // ok
How do I render embedded objects in Apigility? For example, if I have a 'user' object and it composes a 'country' object, should I be rendering the 'country' object as an embedded object? And how should I do this?
I am using the Zend\Stdlib\Hydrator\ArraySerializable. My getArrayCopy() method simply returns an array of properties that I want exposed. The array keys are the property names. The array values are the property values. In the case of user->country, the value is an object, not a scalar.
When I return the user object from UserResource->fetch(), here's how it is rendered:
{
"id": "1",
"firstName": "Joe",
"lastName": "Bloggs",
"status": "Active",
"email": "test#example.com",
"country": {
"code": "AU",
"name": "Australia"
},
"settings": "0",
"_links": {
"self": {
"href": "http://api.mydomain.local/users/1"
}
}
}
Note that 'country' is not in an _embedded field. If it is supposed to be in _embedded, I would have thought that Apigility would automatically do that (since it automatically adds the _links object).
As a related issue, how do I go about returning other rel links, such as back, forward, etc?
The easiest way to get Apigility to render embedded resources is when there is an API/resource associated to the embedded object. What I mean for your example is that you'd have an API resource that has a country entity. In that case, if your getArrayCopy returned the the CountryEntity, Apigility would render it automatically as an embedded resource.
If your getArrayCopy is returning country as an array with code and name, you'll end up with what you saw.
For the other part, the rel links for first, last, prev and next will come from the fetchAll method when you return a Paginator. Your collection extends from this already, but it needs an adapter. The code could look something like this:
public function fetchAll($params)
{
// Return a \Zend\Db\Select object that will retrieve the
// stuff you want from the database
$select = $this->service->fetchAll($params);
$entityClass = $this->getEntityClass();
$entity = new $entityClass();
$hydrator = new \Zend\Stdlib\ArraySerializable();
$prototype = new \Zend\Db\ResultSet\HydratingResultSet($hydrator, $entity);
$paginator = new \Zend\Paginator\Adapter\DbSelect($select, $this->sql, $prototype);
$collectionClass = $this->getCollectionClass();
return new $collectionClass($paginator);
}
There are other paginator adapters as well - an ArrayAdapter which will take in an array of however big and then paginate it so you only get the desired number of results. The downside to this if you use it with database results, you'll potentially be retrieving and discarding a lot of results. The DbSelect paginator will modify the $select object to add the limit and order clause automatically so you only retrieve the bits you need. There are also adapters if you're using DbTableGateway, Iterators or even callbacks. You can also implement your own of course.
Hope this helps. If you have more specific needs or clarification, please comment and I'll do my best.
I posted this example on github.
https://github.com/martins-smb/apigility-renderCollection-example
Hope this helps.