How to avoid Dialyzer errors for protocols? - protocols

A simple protocol yields two kinds of dialyzer warnings:
defmodule Dtest do
defprotocol Valid do
#doc "Returns true if data is in a valid state"
def valid?(data)
end
defimpl Valid, for: Integer do
def valid?(_), do: true
end
end
The warning I can't figure out is this:
dtest.ex:2: The specification for
'Elixir.Dtest.Valid':'__protocol__'/1 states that the function might
also return 'true' but the inferred return is
'Elixir.Dtest.Valid' | 'false' | [{'valid?',1},...]
I also couldn't figure out a #spec that'd work here to silence the warning.
The other kind of warning has been discussed elsewhere – many "unknown functions" listed:
Unknown functions:
'Elixir.Dtest.Valid.Atom':'__impl__'/1
'Elixir.Dtest.Valid.BitString':'__impl__'/1
(etc.)
Is there a #spec that can be used with defprotocol's? I haven't found any examples. Or, is there a way, in the source code to mark the defprotocol to be ignored by dialyzer?
EDIT: Here's the full fix for the first error:
defmodule Dtest do
defprotocol Valid do
#doc "Returns true if data is in a valid state"
#dialyzer {:nowarn_function, __protocol__: 1}
def valid?(data)
end
defimpl Valid, for: Integer do
def valid?(_), do: true
end
end

I'm using
#dialyzer {:nowarn_function, __protocol__: 1}
in the protocol definition for now.

Related

How to have custom enconding for struct using Jason?

Background
I am trying to encode a structure into json format using the Jason library. However, this is not working as expected.
Code
Let's assume I have this struct:
defmodule Test do
defstruct [:foo, :bar, :baz]
end
And that when using Jason.enconde(%Test{foo: 1, bar: 2, baz:3 }) I want this json to be created:
%{"foo" => 1, "banana" => 5}
Error
It is my understanding that to achieve this I need to implement the Jason.Enconder protocol in my struct:
https://hexdocs.pm/jason/Jason.Encoder.html
defmodule Test do
defstruct [:foo, :bar, :baz]
defimpl Jason.Encoder do
#impl Jason.Encoder
def encode(value, opts) do
Jason.Encode.map(%{foo: Map.get(value, :foo), banana: Map.get(value, :bar, 0) + Map.get(value, :baz, 0)}, opts)
end
end
end
However, this will not work:
Jason.encode(%Test{foo: 1, bar: 2, baz: 3})
{:error,
%Protocol.UndefinedError{
description: "Jason.Encoder protocol must always be explicitly implemented.\n\nIf you own the struct, you can derive the implementation specifying which fields should be encoded to JSON:\n\n #derive {Jason.Encoder, only: [....]}\n defstruct ...\n\nIt is also possible to encode all fields, although this should be used carefully to avoid accidentally leaking private information when new fields are added:\n\n #derive Jason.Encoder\n defstruct ...\n\nFinally, if you don't own the struct you want to encode to JSON, you may use Protocol.derive/3 placed outside of any module:\n\n Protocol.derive(Jason.Encoder, NameOfTheStruct, only: [...])\n Protocol.derive(Jason.Encoder, NameOfTheStruct)\n",
protocol: Jason.Encoder,
value: %Test{bar: 2, baz: 3, foo: 1}
}}
From what I understand, it looks like I can only select/exclude keys to serialize, I cannot transform/add new keys.
Since I own the structure in question, using Protocol.derive is not necessary.
However I fail to understand how I can leverage the Jason.Encoder protocol to achieve what I want.
Questions
Is my objective possible using the Jason library, or is this a limitation?
Am I miss understanding the documentation and doing something incorrect?
My guess is, this is due to writing the protocol inside a test file. Protocol consolidation happens before the test file executes, so the protocol never becomes part of the compiled codebase.
To elaborate with an example...
I did the following in a Phoenix app
into the lib folder, I added foo.ex
defmodule Foo do
defstruct [:a, :b]
defimpl Jason.Encoder do
def encode(%Foo{a: a, b: b}, opts) do
Jason.Encode.map(%{"a" => a, "b" => b}, opts)
end
end
end
in the test folder, I added foo_test.exs
defmodule FooTest do
use ExUnit.Case
defmodule Bar do
defstruct [:c, :d]
defimpl Jason.Encoder do
def encode(%Bar{c: c, d: d}, opts) do
Jason.Encode.map(%{"c" => c, "d" => d}, opts)
end
end
end
test "encodes Foo" do
%Foo{a: 1, b: 2} |> Jason.encode!() |> IO.inspect()
end
test "encodes Bar" do
%Bar{c: 5, d: 6} |> Jason.encode!()
end
end
Running this test fule, results in the "encodes Foo" passing, but "encodes Bar" fails with a warning
warning: the Jason.Encoder protocol has already been consolidated, an implementation for FooTest.Bar has no effect. If you want to implement protocols after compilation or during tests, check the "Consolidation" section in the Protocol module documentation
followed by an error in the test
** (Protocol.UndefinedError) protocol Jason.Encoder not implemented for %FooTest.Bar{c: 5, d: 6} of type FooTest.Bar (a struct), Jason.Encoder protocol must always be explicitly implemented.
This is because of protocol consolidation happening, causing the Bar protocol to not be compiled.
You can turn off protocol consolidation in the test environment, by adding the following to mix.exs
def project do
# ...
consolidate_protocols: Mix.env() != :test,
#...
end
If you do that, the protocol will compile and both tests will pass.
However, the solution is probably to just not write the struct/protocol directly in the test file.

how to fix "Rendered ActiveModel::Serializer::Null with Hash"

I am trying to write API for user model, where i have to return only two columns with some modification(appending string)
Every thing work's fine, I even get the correct result, but when I see status code its showing '500', when i check the logs its showed the following error
[active_model_serializers] Rendered ActiveModel::Serializer::Null with Hash
following is the code
1. users_controller.rb
class Api::V1::UsersController < Api::V1::ApiController
# GET
def pl_details
render json: {pl: current_user.pl_url, enabled: current_user.personal_calendar_enabled}, status: :success
end
...
end
user.rb
...
def pl_url
return "#{Rails.application.secrets.app_host}/#{self.calendar_url_token}"
end
...
user_serializer.rb
class UserSerializer < ActiveModel::Serializer
attributes :id, :firstname, :lastname, :email
end
Never mind,
I just did it other way around,I used a separate Serializer to avoid the error, following is the approach
class Api::V1::UsersController < Api::V1::ApiController
# GET
def pl_details
render json: current_user,serializer: PLSerializer, status: :success
end
...
end
and inside PLSerializer
class PLSerializer < ActiveModel::Serializer
attributes :pl, :personal_calendar_enabled
def personal_link
current_user.pl_url
end
end

Invalid Syntax for PEP-8

I'm receiving a notification that advised the below script is not PEP-8:
example_var = print('whoa')
Output:
[E] invalid syntax.
It's showing that the error is a result of the first parentheses in the print statement, but nothing looks off to me.
example_var = print('whoa')
example_var

What does it mean by _._ in Groovy/Spock

I have seen _._ in the source code of a few Spock Specs. Does anyone know what that means?
For example,
def "test something"() {
given:
someClass = Mock(SomeClass)
and:
1 * someClass.someMethod() >> returnSomething
0 * _._
when:
String str = someClass.someMethod().toString()
then:
str == 'returnedValue'
}
_ is like a wildcard.
_.someMethod()
Means the method someMethod called on any mocked object, likewise
myDomain._
Means anything called on myDomain.
And following that logic _._ means anything called on any mock object. This can be useful if you don't want any more methods called:
0 * _._
Extra Reading
You should read the docs for some more information on the kinds of things _ can be used for.

Groovy CliBuilder: only last LongOpt is taken in account

I'm trying to use the groovy CliBuilder to parse command line options. I'm trying to use multiple long options without a short option.
I have the following processor:
def cli = new CliBuilder(usage: 'Generate.groovy [options]')
cli.with {
h longOpt: "help", "Usage information"
r longOpt: "root", args: 1, type: GString, "Root directory for code generation"
x args: 1, type: GString, "Type of processor (all, schema, beans, docs)"
_ longOpt: "dir-beans", args: 1, argName: "directory", type: GString, "Custom location for grails bean classes"
_ longOpt: "dir-orm", args: 1, argName: "directory", type: GString, "Custom location for grails domain classes"
}
options = cli.parse(args)
println "BEANS=${options.'dir-beans'}"
println "ORM=${options.'dir-orm'}"
if (options.h || options == null) {
cli.usage()
System.exit(0)
}
According to the groovy documentation I should be able to use multiple "_" values for an option when I want it to ignore the short option name and use a long option name only. According to the groovy documentation:
Another example showing long options (partial emulation of arg
processing for 'curl' command line):
def cli = new CliBuilder(usage:'curl [options] <url>')
cli._(longOpt:'basic', 'Use HTTP Basic Authentication')
cli.d(longOpt:'data', args:1, argName:'data', 'HTTP POST data')
cli.G(longOpt:'get', 'Send the -d data with a HTTP GET')
cli.q('If used as the first parameter disables .curlrc')
cli._(longOpt:'url', args:1, argName:'URL', 'Set URL to work with')
Which has the following usage message:
usage: curl [options] <url>
--basic Use HTTP Basic Authentication
-d,--data <data> HTTP POST data
-G,--get Send the -d data with a HTTP GET
-q If used as the first parameter disables .curlrc
--url <URL> Set URL to work with
This example shows a common convention. When mixing short and long
names, the short names are often one
character in size. One character
options with arguments don't require a
space between the option and the
argument, e.g. -Ddebug=true. The
example also shows the use of '_' when
no short option is applicable.
Also note that '_' was used multiple times. This is supported but
if any other shortOpt or any longOpt is repeated, then the behavior is undefined.
http://groovy.codehaus.org/gapi/groovy/util/CliBuilder.html
When I use the "_" it only accepts the last one in the list (last one encountered). Am I doing something wrong or is there a way around this issue?
Thanks.
not sure what you mean it only accepts the last one. but this should work...
def cli = new CliBuilder().with {
x 'something', args:1
_ 'something', args:1, longOpt:'dir-beans'
_ 'something', args:1, longOpt:'dir-orm'
parse "-x param --dir-beans beans --dir-orm orm".split(' ')
}
assert cli.x == 'param'
assert cli.'dir-beans' == 'beans'
assert cli.'dir-orm' == 'orm'
I learned that my original code works correctly. What is not working is the function that takes all of the options built in the with enclosure and prints a detailed usage. The function call built into CliBuilder that prints the usage is:
cli.usage()
The original code above prints the following usage line:
usage: Generate.groovy [options]
--dir-orm <directory> Custom location for grails domain classes
-h,--help Usage information
-r,--root Root directory for code generation
-x Type of processor (all, schema, beans, docs)
This usage line makes it look like I'm missing options. I made the mistake of not printing each individual item separate from this usage function call. That's what made this look like it only cared about the last _ item in the with enclosure. I added this code to prove that it was passing values:
println "BEANS=${options.'dir-beans'}"
println "ORM=${options.'dir-orm'}"
I also discovered that you must use = between a long option and it's value or it will not parse the command line options correctly (--long-option=some_value)

Resources