How to provide a custom Deserializer for akka-http query parameters? - akka-http

The akka-http 2.4.7 reference states that it's possible to do custom Deserializers to convert query parameters, without storing them in an intermediate variable:
"amount".as[Int] extract value of parameter "amount" as Int, you need a matching Deserializer in scope for that to work (see also Unmarshalling)
"amount".as(deserializer) extract value of parameter "amount" with an explicit Deserializer
However, the Deserialized parameter sample on that page does not show how custom deserializers are being used.
How do I define one, so that I can say, e.g. .as[MyType]?
I think the documentation is at fault, because I cannot find a Deserializer mentioned anywhere in akka sources: search
Screenshot on how Deserializer is typeset in the Akka docs.

A Deserializer is just a name for an Unmarshaller in akka-http 2.4.7 (same for akka-http 2.4.8)
Edit: Let's say you're extracting a query parameter named type, and you want a Deserializer that can pattern-match that type parameter from a String to a MyType.
Your route that's in your server app may look like this:
object MyServer {
...
// Assuming that you're requiring a
// `type` parameter from a `POST` request
import MyType._
val route =
post {
parameter('type.as(myTypeDeserializer)) { myTypeValue =>
complete {
// `myTypeValue` is already pattern-matched
// to type `MyType` here thanks to `myTypeDeserializer`
...
}
}
}
...
}
Your MyType object may look like this:
object MyType {
case object Type1 extends MyType
case object Type2 extends MyType
case object Type3 extends MyType
import akka.http.scaladsl.unmarshalling.Unmarshaller
// Here we pattern match the query parameter,
// which has type `String`, to a `MyType`
val stringToMyType = Unmarshaller.strict[String, MyType] {
case "type1" => MyType.Type1
case "type2" => MyType.Type2
case "type3" => MyType.Type3
}
}
Hence akka-http will automatically throw a BadRequest response if user requests an unsupported type parameter.

I was able to declare the custom query parameter marshaller right, based on the PredefinedFromStringUnmarshallers.scala built-in samples.
implicit val desId: Unmarshaller[String,Context.Id] = Unmarshaller.strict[String, Context.Id] { s =>
Context.Id.parseOpt(s).getOrElse(
if (s=="") throw Unmarshaller.NoContentException
else throw new IllegalArgumentException( s"Not a valid id: '$s'" )
)
}
Providing the return type explicitly seems to matter.

Related

mockk, how to verify a function is called with Map type and interface type

The class has a function:
fun theFunc(uri: Uri, theMap: Map<String, String>?, callback: ICallback) {
......
}
and would like to verify it is called with proper params type
io.mockk.verify { mock.theFunc(ofType(Uri::class), ofType(Map<String, String>::class), ofType(ICallbak::class)) }
the ofType(Uri::class) is ok,
the ofType(Map<String, String>::class got error:
the ofType(ICallbak::class) got error:
ICallback does not have a companion object, thus must be initialized
here.
How to use the ofType() for Map and interface?
The problem is that generic parameters are lost at runtime due to type erasure, and for this reason the syntax doesn't allow generic parameters to be specified in that context. You can write Map::class but not Map<String, String>::class because a Map<String, String> is just a Map at runtime.
So, you can call it like this:
verify { mock.theFunc(ofType(Uri::class), ofType(Map::class), ofType(ICallback::class)) }
that will work. However, there is also a version of function ofType which takes generic parameters, so you can use this:
verify { mock.theFunc(ofType<Uri>(), ofType<Map<String, String>>(), ofType<ICallback>()) }
You need to use mapOf<String,String>::class
io.mockk.verify { mock.theFunc(ofType(Uri::class), ofType(mapOf<String,String>()::class), ofType(ICallbak)) }
For interface, you can create mocck object. And put it into ofType.
val callbackMock: ICallback = mockk()
io.mockk.verify { mock.theFunc(ofType(Uri::class), ofType(mapOf<String,String>()::class), ofType(callbackMock::class)) }

Unmarshalling generic types with Json Spray in Akka Http

I have routes in Akka Http(Scala) project which are basically the same (CRUD operations) except for entities they operate on
I have my Json formats defined in JsonSupport trait like this:
trait JsonSupport extends SprayJsonSupport {
import DefaultJsonProtocol._
implicit val userJsonFormat = jsonFormat3(User)
}
Then I have a route defined which extends this trait, so it works fine if I use a concrete type, but as soon as I have a generic type it fails to compile:
def userRoute[T]: Route =
pathPrefix("users") {
post {
entity(as[T]) { user =>
with error:
could not find implicit value for parameter um: akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[T]
I suspect that it can't find implicit value because the type is too broad.
What type constraints should I give the T so it would be able to resolve it?
Is there a way to solve it?
Cheers,
Leonti

Dart serializing immutable objects

I would like serialize this immutable class
class CatalogueItem {
final Uri source;
final DateTime analyis;
final Period fromTo;
CatalogueItem.create(this.source, this.analyis, this.fromTo);
}
But I cannot as it is not a simple class. From the web site https://www.dartlang.org/articles/serialization/
Simple: All of the objects to be serialized are data transfer objects
(DTOs) with a default constructor.
So I have to add a default constructor - which means I have to drop the final keywords and my class is no longer immutable.
class CatalogueItem {
Uri source;
DateTime analyis;
Period fromTo;
CatalogueItem.create(this.source, this.analyis, this.fromTo);
CatalogueItem(){}
}
Is there any way around this one?
I think the default constructor is only necessary for deserialization (never used a package for (de)serialization). Serialization shouldn't need it.
The default constructor is redundant because if the deserialization package needs a default constructor it obviously attempts to create an instance using the default constructor to afterwards set the field values, which can't work with final fields.
I don't know if a serialization package supports a custom toJson() method/fromJson() constructor but I think this would be the easiest way to go.
class CatalogueItem {
final Uri source;
final DateTime analysis;
final Period fromTo;
CatalogueItem.create(this.source, this.analysis, this.fromTo);
factory CatalogueItem.fromJson(Map json) {
return new CatalogueItem.create(
json['source'] == null ? null : Uri.parse(json['source']),
json['analysis'] == null ? null : DateTime.parse(json['analysis'])),
json['fromTo'] == null ? null : new Period.fromJson(json['fromTo']));
}
Map toJson() {
return {
'source': source == null ? null : '$source',
'analysis': analysis == null ? null : '$analysis',
'fromTo': fromTo == null ? null : fromTo.toJson();
}
}
https://github.com/google/built_value.dart may do what you want -- it is specifically for creating immutable classes and serializing them.
Note that this requires a slightly different form for the class. This is to allow built_value to generate an implementation for you, and serializers.
abstract class CatalogueItem
implements Built<CatalogueItem, CatalogueItemBuilder> {
static Serializer<CatalogueItem> get serializer
=> _$catalogueItemSerializer;
Uri get source;
DateTime get analyis;
Period get fromTo;
factory CatalogueItem([updates(CatalogueItemBuilder b)]) =
_$CatalogueItem;
CatalogueItem._();
}
The generated implementation is immutable (uses final), and also provides operator==, hashCode and toString.
More detailed example: https://github.com/google/built_value.dart/blob/master/example/lib/example.dart
One option is to read further in the article and use the serialization package, which does handle such cases.

Using asType with Mixin annotation

I'd like write a custom type conversion Category in Groovy. The goal is to assign the values of a Map to the fields of a Groovy bean. In the future there will be different response types. The values of of the Map are always of type String but will have to be converted into a different data type. To make this work I created a Category class that implements a method named asType. This is a simplified example of my code:
class MapCategory {
static Object asType(Map self, Class clazz) {
if(clazz == Response) {
Response response = new Response()
self.each { key, value ->
response.setProperty(key, value)
}
return response
}
DefaultGroovyMethods.asType(self, clazz)
}
}
class Response {
String result
String message
}
This works just fine when when I apply the category using the use keyword.
use(MapCategory) {
println [result: 'OK', message: 'Success'] as Response
}
However, when I try to use the #Mixin annotation instead it doesn't seem to work correctly. I get the correct response type but all fields are null.
#Mixin(MapCategory)
class MyClass {
def printResponse() {
println [result: 'OK', message: 'Success'] as Response
}
}
Does anybody know why it doesn't work correctly using the annotation?
Mixins don't work that way. You are trying to mix in the method for Map into your MyClass object. The mixin would only work if MyClass extended Map.
Instead, you want to use the use keyword like normal, and just use your category as a category.
Alternatively, you might not need it at all. Did you know that you can, by default, convert any map into any GroovyBean without extra code? Just use the map-based constructor, like so:
#groovy.transform.Canonical // Groovy 1.8, just added for automatic toString method
class Response {
String result
String message
}
println new Response([result: 'OK', message: 'Success'])
println([result: 'bad', message: 'blah'] as Response)
Notice, automatic Map conversion works both ways. It's a built-in feature of Groovy.
Of course, if you need something more complex than just assigning bean properties, this won't help.
Note: I'd give you a link, but the Groovy website appears to be broken, and I can't find code examples. :-(
EDIT: Another Suggestion
Instead of using a Category at all, why don't you let the bean itself handle it:
#groovy.transform.Canonical
class Response {
String result
String message
int num
public void setNum(String num) {
this.num = Integer.parseInt(num)
}
}
def map = [result: 'OK', message: 'Success', num: '35' ]
println map as Response

Does PetaPoco handle enums?

I'm experimenting with PetaPoco to convert a table into POCOs.
In my table, I've got a column named TheEnum. The values in this column are strings that represent the following enum:
public enum MyEnum
{
Fred,
Wilma
}
PetaPoco chokes when it tries to convert the string "Fred" into a MyEnum value.
It does this in the GetConverter method, in the line:
Convert.ChangeType( src, dstType, null );
Here, src is "Fred" (a string), and dstType is typeof(MyEnum).
The exception is an InvalidCastException, saying Invalid cast from 'System.String' to 'MyEnum'
Am I missing something? Is there something I need to register first?
I've got around the problem by adding the following into the GetConverter method:
if (dstType.IsEnum && srcType == typeof(string))
{
converter = delegate( object src )
{
return Enum.Parse( dstType, (string)src ) ;
} ;
}
Obviously, I don't want to run this delegate on every row as it'll slow things down tremendously. I could register this enum and its values into a dictionary to speed things up, but it seems to me that something like this would likely already be in the product.
So, my question is, do I need to do anything special to register my enums with PetaPoco?
Update 23rd February 2012
I submitted a patch a while ago but it hasn't been pulled in yet. If you want to use it, look at the patch and merge into your own code, or get just the code from here.
I'm using 4.0.3 and PetaPoco automatically converts enums to integers and back. However, I wanted to convert my enums to strings and back. Taking advantage of Steve Dunn's EnumMapper and PetaPoco's IMapper, I came up with this. Thanks guys.
Note that it does not handle Nullable<TEnum> or null values in the DB. To use it, set PetaPoco.Database.Mapper = new MyMapper();
class MyMapper : PetaPoco.IMapper
{
static EnumMapper enumMapper = new EnumMapper();
public void GetTableInfo(Type t, PetaPoco.TableInfo ti)
{
// pass-through implementation
}
public bool MapPropertyToColumn(System.Reflection.PropertyInfo pi, ref string columnName, ref bool resultColumn)
{
// pass-through implementation
return true;
}
public Func<object, object> GetFromDbConverter(System.Reflection.PropertyInfo pi, Type SourceType)
{
if (pi.PropertyType.IsEnum)
{
return dbObj =>
{
string dbString = dbObj.ToString();
return enumMapper.EnumFromString(pi.PropertyType, dbString);
};
}
return null;
}
public Func<object, object> GetToDbConverter(Type SourceType)
{
if (SourceType.IsEnum)
{
return enumVal =>
{
string enumString = enumMapper.StringFromEnum(enumVal);
return enumString;
};
}
return null;
}
}
You're right, handling enums is not built into PetaPoco and usually I just suggest doing exactly what you've done.
Note that this won't slow things down for requests that don't use the enum type. PetaPoco generates code to map responses to pocos so the delegate will only be called when really needed. In other words, the GetConverter will only be called the first time a particular poco type is used, and the delegate will only be called when an enum needs conversion. Not sure on the speed of Enum.Parse, but yes you could cache in a dictionary if it's too slow.
If you are using PetaPoco's T4 generation and you want enums in your generated type, you can use the PropertyType override in Database.tt:
tables["App"]["Type"].PropertyType = "Full.Namespace.To.AppType";
I you want to store the value of the enum instead of the index number (1,2,4 for example) you can locate the update function in PetaPoco class because the code is "managed" etc, when you add it as nuget package it will store the .cs file to your project. If we would have the enum variable Color = {red, yellow, blue}
Instead of:
// Store the parameter in the command
AddParam(cmd, pc.GetValue(poco), pc.PropertyInfo);
change to:
//enum?
if (i.Value.PropertyInfo.PropertyType.IsEnum)
{
AddParam(cmd, i.Value.GetValue(poco).ToString(), i.Value.PropertyInfo);
}
else
{
// Store the parameter in the command
AddParam(cmd, i.Value.GetValue(poco), i.Value.PropertyInfo);
}
It would store "yellow" instead of 2