Create Granite model from JSON params - crystal-lang

What I would like to have happen: Someone can make a post request to users/new with parameters, and I would like to create a User object from the JSON parameters.
In the readme, it gives this example:
foo = Foo.from_json(%({"name": "Granite1"}))
But when I try to do this I get this compile-time error:
in /usr/local/Cellar/crystal/0.26.1/src/json/pull_parser.cr:13: no
overload matches 'JSON::Lexer.new' with type Hash(String, Array(JSON::Any) | Bool | Float64 | Hash(String, JSON::Any) | Int64 | String | Nil)
Overloads are:
- JSON::Lexer.new(string : String)
- JSON::Lexer.new(io : IO)
- JSON::Lexer.new()
#lexer = Lexer.new input
^~~
Here is what env.params.json looks like when logged to the console:
{"name" => "test",
"username" => "tester",
"email" => "test",
"password" => "test"}
Any help would be much appreciated.

The compiler is steering you in the right direction here. It looks like you're passing in a variable that, at compile-time, has the type Hash(String, V) where V is one of the types
Array(JSON::Any)
Bool
Float64
Hash(String, JSON::Any)
Int64
String
Nil
What it's expecting is a String (or an IO object, which is similar to a String) of JSON. That's what you have in the example. %(foo) is another way to create the String "foo" (See "Percent string literals" in the guide for more info). They're using it here because it allows you to avoid escaping the double quotes used in the JSON.
Based on the compile-time type that Crystal has given your parameter, my guess is that it's already been converted from JSON into a Crystal Hash. Double-check that you're not parsing it twice.
Without seeing the source, there's not much more information I can provide, but I hope that helps.

Related

How to map JSON::Any to custom Object in Crystal language?

How to map parsed JSON as JSON::Any type to custom Object?
In my case, I am working on chat client. Chat API can respond to requests with following JSON:
{"ok" => true,
"result" =>
[{"update_id" => 71058322,
"message" =>
{"message_id" => 20,
"from" => "Benjamin",
"text" => "hey"}}]}
Somewhere inside my API client code I parse these JSON to perform some basic health checks and pass the result to response consumer. In the consumer, I iterate over result array and try to convert each update to appropriate object:
module Types
class Update
JSON.mapping({
update_id: {type: Int32},
message: {type: Message},
})
end
end
module Types
class Message
JSON.mapping({
message_id: Int32,
date: Int32,
text: String,
})
end
end
return unless response["ok"]
response["result"].each do |data|
update = Types::Update.from_json(data)
end
Unfortunately, last line results in compile error:
no overload matches 'JSON::Lexer.new' with type JSON::Any
Apparently, Object.from_json can accept only String JSONs, but not parsed JSON. In my case data is JSON::Any object.
Dirty fix Types::Update.from_json(data.to_json) works, but it looks ridiculous.
What is the proper way to map JSON object to custom type preserving all nested structure?
JSON.mapping doesn't work nicely together with JSON.parse. To solve your problem you can create another mapping Types::Result and parse a hole json using Object.from_json which is even much more convenient to work with:
module Types
class Message
JSON.mapping(
message_id: Int32,
text: String
)
end
class Update
JSON.mapping(
update_id: Int32,
message: Message
)
end
class Result
JSON.mapping(
success: { key: "ok", type: Bool },
updates: { key: "result", type: Array(Update) }
)
end
end
result = Types::Result.from_json string_json
result.success # => true
result.updates.first.message.text # => "hey"

Type assertion on Goa package (uuid.UUID)

I'm testing out Goa for an API. I want to use uuid as an ID data type. I modified the following function in controller.go:
// Show runs the show action.
func (c *PersonnelController) Show(ctx *app.ShowPersonnelContext) error {
// v4 UUID validate regex
var validate = `^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[8|9|aA|bB][a-f0-9]{3}-[a-f0-9]{12}$`
uuidValidator := regexp.MustCompile(validate)
if !uuidValidator.Match(ctx.MemberID) {
return ctx.NotFound()
}
// Build the resource using the generated data structure
personnel := app.GoaCrewWorkforce{
MemberID: ctx.MemberID,
FirstName: fmt.Sprintf("First-Name #%s", ctx.MemberID),
LastName: fmt.Sprintf("Last-Name #%s", ctx.MemberID),
}
What I would like to do is validate a v4 uuid in my controller using Regexp so that it does not poll the server if it does not validate. It's my understanding a uuid is a [16]byte slice. Regexp has a Match []byte function. Yet I can't seem to understand why I get the following error:
cannot use ctx.MemberID (type uuid.UUID) as type []byte in argument to uuidValidator.Match
How can I type assert ctx.MemberID? I don't think its possible to do a cast conversion in this case? Any guidance is appreciated.
If you want to validate the uuid, you can check the bits directly. There's not much to verify, since most of the 16 bytes are random, but you can check the top 4 bits of the 6th byte for the version number, or the top 3 bits of the 8th byte for the variant.
// enforce only V4 uuids
if ctx.MemberID[6] >> 4 != 4 {
log.Fatal("not a V4 UUID")
}
// enforce only RFC4122 type UUIDS
if (ctx.MemberID[8]&0xc0)|0x80 != 0x80 {
log.Fatal("not an RFC4122 UUID")
}

How to handle return types with multiple fields

I am calling a method "get_text" on GText.buffer detailed here http://oandrieu.nerim.net/ocaml/lablgtk/doc/GText.buffer.html
let text = textView#buffer#get_text in
However as get_text returns multiple values, when I try to use my variable "text" as a string, for example
textView2#buffer#set_text text;
I get the following error message:
Error: This expression has type
?start:GText.iter ->
?stop:GText.iter -> ?slice:bool -> ?visible:bool -> unit -> string
but an expression was expected of type string
How can I access the string being returned by the method? In general, how can I separate the multiple values returned by a method so I can access and use them individually?
I just looked up your link to lablgtk - it looks like you are missing the ():
let text = textView#buffer#get_text () in ...
The problem with this kind of error is that you are using a (curried) function where a string is required, and the message about the type error sounds kind of "long winded" and not to the point.

Query parameters for GET requests using Akka HTTP (formally known as Spray)

One of the features of Akka HTTP (formally known as Spray) is its ability to automagically marshal and unmarshal data back and forth from json into case classes, etc. I've had success at getting this to work well.
At the moment, I am trying to make an HTTP client that performs a GET request with query parameters. The code currently looks like this:
val httpResponse: Future[HttpResponse] =
Http().singleRequest(HttpRequest(
uri = s"""http://${config.getString("http.serverHost")}:${config.getInt("http.port")}/""" +
s"query?seq=${seq}" +
s"&max-mismatches=${maxMismatches}" +
s"&pam-policy=${pamPolicy}"))
Well, that's not so pretty. It would be nice if I could just pass in a case class containing the query parameters, and have Akka HTTP automagically generate the query parameters, kind of like it does for json. (Also, the server side of Akka HTTP has a somewhat elegant way of parsing GET query parameters, so one would think that it would also have a somewhat elegant way to generate them.)
I'd like to do something like the following:
val httpResponse: Future[HttpResponse] =
Http().singleRequest(HttpRequest(
uri = s"""http://${config.getString("http.serverHost")}:${config.getInt("http.port")}/query""",
entity = QueryParams(seq = seq, maxMismatches = maxMismatches, pamPolicy = pamPolicy)))
Only, the above doesn't actually work.
Is what I want doable somehow with Akka HTTP? Or do I just need to do things the old-fashioned way? I.e, generate the query parameters explicitly, as I do in the first code block above.
(I know that if I were to change this from a GET to a POST, I could probably to get it to work more like I would like it to work, since then I could get the contents of the POST request automagically converted from a case class to json, but I don't really wish to do that here.)
You can leverage the Uri class to do what you want. It offers multiple ways to get a set of params into the query string using the withQuery method. For example, you could do something like this:
val params = Map("foo" -> "bar", "hello" -> "world")
HttpRequest(Uri(hostAndPath).withQuery(params))
Or
HttpRequest(Uri(hostAndPath).withQuery(("foo" -> "bar"), ("hello" -> "world")))
Obviously this could be done by altering the extending the capability of Akka HTTP, but for what you need (just a tidier way to build the query string), you could do it with some scala fun:
type QueryParams = Map[String, String]
object QueryParams {
def apply(tuples: (String, String)*): QueryParams = Map(tuples:_*)
}
implicit class QueryParamExtensions(q: QueryParams) {
def toQueryString = "?"+q.map{
case (key,value) => s"$key=$value" //Need to do URL escaping here?
}.mkString("&")
}
implicit class StringQueryExtensions(url: String) {
def withParams(q: QueryParams) =
url + q.toQueryString
}
val params = QueryParams(
"abc" -> "def",
"xyz" -> "qrs"
)
params.toQueryString // gives ?abc=def&xyz=qrs
"http://www.google.com".withParams(params) // gives http://www.google.com?abc=def&xyz=qrs

Scala Play2 Error on Web Service call

I'm getting an error on compile with the following code.
I'm trying to call a Web Service.
def authenticate(username: String, password: String): String = {
val request: Future[Response] =
WS.url(XXConstants.URL_GetTicket)
.withTimeout(5000)
.post( Map("username" -> Seq(username), "password" -> Seq(password) ) )
request map { response =>
Ok(response.xml.text)
} recover {
case t: TimeoutException =>
RequestTimeout(t.getMessage)
case e =>
ServiceUnavailable(e.getMessage)
}
}
I'm seeing the following compiler error:
type mismatch; found : scala.concurrent.Future[play.api.mvc.SimpleResult[String]] required: String
The value being returned from your authenticate function is val request = ... which is of type Future[Response] but the function expects a String which as the compiler says is a type mismatch error. Changing the return type of the function to Future[Response] or converting request to a String before returning it should fix it.
Like say Brian, you're currently returning a Future[String], when you method said that you want to return a String.
The request return a Future because it's an asynchronous call.
So, you have two alternatives:
Change your method definition to return a Future[String], and manage this future in another method (with .map())
Force the request to get this result immediately, in a synchronous way. It's not a very good deal, but sometimes it's the simplest solution.
import scala.concurrent.Await
import scala.concurrent.duration.Duration
val response: String = Await.result(req, Duration.Inf)