Joi: Require exactly two of three fields to be non-empty - joi

Here is a simple version of my schema.
var schema = Joi.object().keys({
a: Joi.string(),
b: Joi.string(),
c: Joi.string()
});
I want a, b, c to be exactly 2 out of 3 non-empty. I.e.:
if a, b are not empty, c should not be set
idem with circular permutation of a,b,c
of course, if 2 or more are empty, throw an error too
Tried using .or() but obviously doesn't do the trick. Looked into .alternatives() but didn't get it working.

It's tricky to find an elegant way to handle this without stumbling into circular dependency issues. I've managed to get something working using .alternatives() and .try().
The solution in its raw form would be this:
Joi.alternatives().try(
Joi.object().keys({
a: Joi.string().required(),
b: Joi.string().required(),
c: Joi.string().required().valid('')
}),
Joi.object().keys({
a: Joi.string().required().valid(''),
b: Joi.string().required(),
c: Joi.string().required()
}),
Joi.object().keys({
a: Joi.string().required(),
b: Joi.string().required().valid(''),
c: Joi.string().required()
})
);
It's certainly not pretty and could get pretty bloated if any more dependencies are introduced.
In an attempt to reduce the amount of repetition, the following would also work:
var base = {
a: Joi.string().required(),
b: Joi.string().required(),
c: Joi.string().required()
};
Joi.alternatives().try(
Joi.object().keys(Object.assign({}, base,
{
a: base.a.valid('')
})),
Joi.object().keys(Object.assign({}, base,
{
b: base.b.valid('')
})),
Joi.object().keys(Object.assign({}, base,
{
c: base.c.valid('')
}))
);

Related

entity decode for json that return a list/seq of any val

I am building the back-end of an app using http4s. In the app i receive a json response from an external api (not the one i am working on). The api response is of this pattern below.
json response:
`{
"datatable" : {
"data" : [["AAPl", "MRT", "2020-03-20", 123, 123, 12.4, 233, 3234],
["AAPl", "MRT", "2020-03-20", 123, 123, 12.4, 233, 3234]],
"meta": {
"next_date : null
}
}`
my question is?
can someone show me how to create an entity decoder and entity encoder that would decode the pattern. i can seem to get it to work.
currently i have:
object Stocks {
case class tickerInfo(ticker: String, dim:String, date: String, a: Int, b: Int, c: Float, d: Int, e: Int)
case class data(data: Seq[tickerInfo])
case class meta(next_date : Option[String])
case class table(data: data, meta:meta)
case class stockInfo(datatable:table)
object stockInfo {
implicit def stockInfoEntityDecoder[F[_]:Sync]: EntityDecoder [F, stockInfo] = jsonOf
implicit def stockInfoEntityEncoder[F[_] : Applicative]: EntityEncoder[F, stockInfo] = jsonEncoderOf
}
val decodedJson = C.expect[stockInfo](GET(Uri.uri("www.externalApi.com")
}
But this doesn't work. Please can someone tell me where i am going wrong.
I am getting a run time error *not a compile error). and its an http4s error that says - InvalidMessageBodyFailure.
Thanks
You have a couple of errors in your model, but the main problem is that circe won't be able to automatically decode an array of jsons into a case class.
If you can not modify the source of the data, you would need to create your own custom codec.
object Stocks {
final case class TickerInfo(ticker: String, dim: String, date: String, a: Int, b: Int, c: Float, d: Int, e: Int)
final case class Meta(next_date : Option[String])
final case class Table(data: List[TickerInfo], meta: Meta)
final case class StockInfo(datatable: Table)
object StockInfo {
implicit final val TickerInfoDecoder: Decoder[TickerInfo] = Decoder[List[Json]].emap {
case ticker :: dim :: date :: a :: b :: c :: d :: e :: Nil =>
(
ticker.as[String],
dim.as[String],
date.as[String],
a.as[Int],
b.as[Int],
c.as[Float],
d.as[Int],
e.as[Int]
).mapN(TickerInfo).left.map(_.toString)
case list =>
Left(s"Bad number of fields in: ${list.mkString("[", ", ", "]")}")
}
implicit final val MetaDecoder: Decoder[Meta] = deriveDecoder
implicit final val TableDecoder: Decoder[Table] = deriveDecoder
implicit final val StockInfoDecoder: Decoder[StockInfo] = deriveDecoder
}
}
(you can see it working here, I leave outside the http4s part as it would be tricky to mock, but it shouldn't matter)
The error reporting of that custom encoder could be improved by providing more useful messages for each field. That is left as an exercise for the reader.
You need to:
create a data model, presumably consisting of some case classes and possibly sealed traits. What this data model should look like depends on the external API that you're talking to.
create JSON decoders for this data model. These should go in the case classes' companion objects, because that will allow the compiler to find them when needed without having to import anything.
Use the http4s-circe library in order to integrate these decoders with http4s. https://http4s.org/v0.19/json/
If you did everything correctly, you should then be able to use the Http4s client to retrieve the data, e. g. httpClient.expect[YourModelClass](Uri.uri("http://somewhere.com/something/")).
Welcome! I'd probably start inside and work your way out:
So for elements in the data array, perhaps something like (I'm guessing on the domain):
case class Stock(
name: String,
mart: String,
datePosted: LocalDate,
a: Int,
b: Int,
price: Double,
c: Int,
d: Int
)
Using Circe's automatic derevation should handle this pretty well.
Data seems like an array of arrays of exploded elements? So you may have to write some extraction logic to convert to the inner Object manually if that is the case. Something like this in the Controller may help:
elem match {
case name :: mart :: date :: a :: b :: price :: c :: d :: Nil => Stock(name, mart, date, a, b, price, c, d)
case invalid # _ => log.warn(s"Invalid record: $invalid")
}
Hopefully the above snippet returns something useful like an Either[E, A], etc.
Finally, you'd need objects for the outer JSON. Something simple to capture, such as case class ExternalApiRequest(dataTable: T), where T is a type appropriate for the above case. List[String] in the worst case?
Hope this helped! Let me know if you have any specific errors you were running into

How to use nested JSON in schema

Trying to setup a nested schema as follows. I want be able to reject the schema if bb.c is present when aa.a is present.
I've tried without as well as xor
{
Joi.object().keys({
aa: Joi.object().keys({
a: Joi.string(),
b: Joi.string()
}).unknown(true).with("a", "b"),
bb: Joi.object().keys({
c: Joi.string()
}).unknown(true)
}).xor( "aa.a" , ["bb.c"])
}
With the below object xor fails with ValidationError: "value" must contain at least one of [aa.a, bb.c] yet aa.a exists in the supplied values
{
"aa": {
"a": "fg",
"b": "fg"
},
"bb": {
"c": "l"
}
}
If I try
.without( "aa.a" , ["bb.c"])
then the schema passes although in my mind it should not pass as without should fail when bb.c is present along with aa.a
Is it because the two things are nested in other objects perhaps?
Can we not specify deeply linked stuff like this?
Thanks in advance
You can to use Joi.when() and create a schema like this:
Joi.object({
aa: Joi.object().keys({
a: Joi.string(),
b: Joi.string()
}).unknown(true).with('a', 'b'),
bb: Joi.object().keys({
c: Joi.string()
}).unknown(true)
.when('aa.a', {
is: Joi.exist(),
then: Joi.object({ c: Joi.forbidden() })
})
});
Basically what this does is that if aa.a is present then the bb.c is not allowed and the schema will fail it's validation. With this schema your example will fail as you expect.

scala assign value to object

I have the following code:
def getContentComponents: Action[AnyContent] = Action.async {
contentComponentDTO.list().map(contentComponentsFuture =>
contentComponentsFuture.foreach(contentComponentFuture =>
contentComponentFuture.typeOf match {
case 5 =>
contentComponentDTO.getContentComponentText(contentComponentFuture.id.get).map(
text => contentComponentFuture.text = text.text
)
}
)
Ok(Json.toJson(contentComponentsFuture))
)
and get this error message while assigning a value:
Is there a way to solve this issue?
I thought about creating a copy but that would mean that I have do lots of other stuff later. It would be much easier for me if I could reassign the value.
thanks
Unfortunately this is not possible as contentComponentFuture.text is an immutable property so you cannot change its value like that.
The only way to "change" the values of the job object is to actually create a totally new instance and discard the old one. This is standard practice when dealing with immutable objects.
Sorry for the bad news.
Just found the solution ...
In model I have to define the Attributes as var
case class ContentComponentModel(
id: Option[Int] = None,
typeOf: Int,
name: String,
version: String,
reusable: Option[Boolean],
createdat: Date,
updatedat: Date,
deleted: Boolean,
processStepId: Int,
var text: Option[String],

Implementing my HbaseConnector

I would like to implement a HbaseConnector.
I'm actually reading the guide but there is a part that I don't understand and I can't find any information about it.
In the part 2 of the guide we can see the following code :
case class HBaseRecord(col0: String, col1: Boolean,col2: Double, col3: Float,col4: Int, col5: Long, col6: Short, col7: String, col8: Byte)
object HBaseRecord {def apply(i: Int, t: String): HBaseRecord = { val s = s”””row${“%03d”.format(i)}””” HBaseRecord(s, i % 2 == 0, i.toDouble, i.toFloat, i, i.toLong, i.toShort, s”String$i: $t”, i.toByte) }}
val data = (0 to 255).map { i => HBaseRecord(i, “extra”)}
I do understand that they store the future column in the HbaseRecord case class but I don't understand the specific use of this line :
val s = s”””row${“%03d”.format(i)}”””
Could someone care to explain ?
It is used to generate row ids like row001, row002 etc. which will populate column0 of your table. Try out simpler way with function
def generate(i: Int): String = { s"""row${"%03d".format(i)}"""}

Dedupe MongoDB Collection

I'm new to NoSQL, so sorry if this is very basic. Let's say I have the following collection:
{
a: 1,
b: 2,
c: 'x'
},
{
a: 1,
b: 2,
c: 'y'
},
{
a: 1,
b: 1,
c: 'y'
}
I would like to run a "Dedupe" query on anything that matches:
{
a: 1,
b: 2
... (any other properties are ignored) ...
},
So after the query is run, either of the following remaining in the collection would be fine:
{
a: 1,
b: 2,
c: 'y'
},
{
a: 1,
b: 1,
c: 'y'
}
OR
{
a: 1,
b: 2,
c: 'x'
},
{
a: 1,
b: 1,
c: 'y'
}
Just so long as there's only one document with a==1 and b==2 remaining.
If you always want to ensure that only one document has any given a, b combination, you can use a unique index on a and b. When creating the index, you can give the dropDups option, which will remove all but one duplicate:
db.collection.ensureIndex({a: 1, b: 1}, {unique: true, dropDups: true})
This answer hasn't been updated in a while. It took me a while to figure this out. First, using the Mongo CLI, connect to the database and create an index on the field which you want to be unique. Here is an example for users with a unique email address:
db.users.createIndex({ "email": 1 }, { unique: true })
The 1 creates the index, along with the existing _id index automatically created.
Now when you run create or save on an object, if that email exists, Mongoose will through a duplication error.
I don't know of any commands that will update your collection in-place, but you can certainly do it via temp storage.
Group your documents by your criteria (fields a and b)
For each group pick any document from it. Save it to temp collection tmp. Discard the rest of the group.
Overwrite original collection with documents from tmp.
You can do this with MapReduce or upcoming Aggregation Framework (currently in unstable branch).
I decided not to write code here as it would take the joy of learning away from you. :)