This moves the nested Models into a more distinct top-level namespace.
There aren't any functional changes here, but it prefaces future work to
better define the parsed (AST) representation of a Thrift file.
* Handle special floats like NaN, inf, -inf
* Add NaN struct
* Write large number with underscores
* Move Thrift.NaN and remove unnecessary function clause
* Follow consistent code style
Thrift uses the `namespace` keyword to define a language's namespace.
namespace elixir Thrift.Namespace
Unfortunately, the Apache Thrift compiler will produce a warning on this
line because it doesn't recognize `elixir` as a supported language.
While that warning is benign, it can be annoying. For that reason, you
can also specify your Elixir namespace as a "magic" namespace comment:
#@namespace elixir Thrift.Namespace
This alternate syntax is borrowed from Scrooge, which uses the same
trick for defining `scala` namespaces.
Previously all backends were removed which would hide all logs. This
is quite inconvenient if a test fails. Capture log will only show
output for failed tests (grouped and inline with each test failure).
Do not log expected errors (tcp socket will always close eventually).
Elixir 1.6's ExUnit implementation changed such that setup_all's
`context` now contains a `:module` key instead of a `:case` key.
Backwards compatibility was intended, but it looks like the ExUnit
runner now only passes `:module`.
This is more consistent with Elixir's own naming conventions. It also
removes a small amount of ambiguity with code that refers to Thrift's
exceptions; that is, things named "exceptions" in this library are
nearly all Thrift exceptions.
This test has been flaky because it relies on race conditions.
Force synchronization using :sys.get_state/1 and remove
assertions that can't be guaranteed.
These were introduced to avoid potential conflicts between our options
parameter (named `opts`) and an `opts` function paramter named by the
Thrift IDL.
This resulted in doubling the number of generated methods (as well as
another level of function call indirection), in addition to making the
resulting user-callable API a little bit more complex.
Instead, go back to using just two generated functions (the "regular"
call and the "bang!" exception-raising variant) and rename our options
parameter to `rpc_opts`, which is very unlikely to conflict in practice.
Passing 0 as the port tells the TCP subsystem to select any free port,
and we can querying the server's listening port via a {:get, :port}
call.
This lets us drop the random_port() helper function and makes things
simpler overall.
In addition to the moderate state-management complexity introduced by
this feature, we've also found that implementing message-level retry
logic directly within a Thrift client to not be ideal. Instead, retries
are better implemented by either application logic (which has complete
understanding of the message payloads, idempotence, etc.) or closer to
the wire level, which can make stronger guarantees about whether the
original message was or was not delivered.
We also no longer attempt to reconnect from the disconnect/2 path.
Instead, we :stop with a clear error reason.
Previously, we expected the "struct name" portion of the input name to
be a suitable module name string (for Module.concat/1). Lowercased names
failed this assumption because they're not valid aliases.
This change reworks the destination module name processing to ensure
that the "struct name" string starts with an initial capitalize letter.
Fixes#297
* Handle reserved keywords
[Similarly to apache/thrift](19baeefd8c/compiler/cpp/src/thrift/thriftl.ll (L273)), we disallow keywords being used in the Thrift IDL
However the keywords restriction only applies to exact-casing, so for example we must support "AND" as a valid enum. When down-casing this value, it conflicts with the Elixir keyword "and", and so using some macro magic we can get around this limitation.
Additionally, since we down-case method names too, similar logic applies when generating a client. The server implementation did not require any changes.
Fixes: #294
* Remove stray IO.inspect
* Add an 'error parsing' test case and fix typo
This addresses nearly all of the warnings in the test files. It also
tweaks the LargeNumbers rule to only apply to numbers over 99999, which
helps avoid a lot of additional transformations.
This also includes additional commentary on the implementation as well
as a small optimization for the case where a void function doesn't throw
any exceptions.
* Typedefs in other modules weren't working properly
Due to the way typedefs work (they're different than everything else),
resolution wasn't working properly on them, this commit adds proper
resolution so you can define them in remote modules.
Fixes#268
Annotations appear within parentheses as part of various Thrift IDL
types such as fields and structures. They can be used to hint the
compiler or inform runtime behavior; the specification is pretty
open-ended.
This change adds support for parsing annotations (whose presence
previously resulted in parse errors) and storing them on our parser
models.
We currently support annotations on:
- Enums
- Fields
- Functions
- Services
- Structures, Unions, and Exceptions
We can parse annotations on typedefs but don't currently store them
anywhere due to the way we internally represent a schema's typedefs as a
simple map of strings to types. We can revisit that later, perhaps as
part of a broader typedef refactoring.
This brings TApplicationException more in line with Elixir's conventions
for creating exceptions. `exception/1` now expects a keyword list with a
:type and an optional :message value. :type can either be an integer or
one of our internal atoms (e.g. :invalid_protocol).
Only the atom form is stored internally. It can be lazily converted back
to an integer at serialization time using `type_id/1`. We don't store
the integer form up-front because it's only used in "exceptional" (ha!)
cases in the server code path.
The list of well-known exception types has been expanded based on the
current Apache Thrift 0.10.0 release (C++ implementation).
Lastly, TApplicationException now has dedicated unit test coverage.
We were previously writing constants to modules named after their
originating files. This approach was unfortunately too naive because the
resulting module names could conflict with other generated modules that
are spelled the same but different only in letter case.
For example, for a file named `myservice.thrift`:
const float PI = 3.14
service MyService {}
We would previously write the constants to `myservice.Myservice`
(`myservice.ex`) and the service definition to `myservice.MyService`
(`my_service.ex`). We now write both to `myservice.MyService` (thanks to
the generator's constants-merging logic) and no longer run into module
name conflicts.
Previously, we defaulted to generating our output files in the root, but
that behavior is potentially dangerous because it could class with other
top-level module names.
It's still possible to write files to the root by specifying `""` as the
namespace.
This is useful information, but we don't currently have a good way to
capture it for later display. As is, printing out these warnings as part
of the parsing path can product a bit of spurious output as part of
normal operations because our mix compile task always parses .thrift
files to determine their build targets.
Change #228 allowed the default namespace to be either an atom or a
string, but we still want to special-case the `nil` atom to mean "no
default namespace". Otherwise, we get a namespace named "Nil".
We had the concept of global resolution, that is, we would place the
current module's definitions in a global scope. This would have worked,
but it wasn't deterministic.
Now, we tell the file group which module is local, and have it update
its resolutions with only the local definitions when we build their
data. This limits the scope of global variables.
This is an incomplete solution to the fact that our existing resolution
merge was non-deterministic with regard to non-qualified names. We now
always take the newer value for a conflicting key, which gets us closer
to the expected behavior.
We unfortunately still aren't implementing the correct scoping rules.
For example, this still allows a non-qualified name to "leak" from one
included file into another. This should be revisited and addressed with
a more complete solution.
The Apache Thrift compiler auto-assigns negative values (starting at -1)
to fields with implicit indices. This changes our code to do the same.
Because field identifiers are serialized on the wire, it's important for
our implementations to match in this regard.
This also moves our implicit identifier warning directly into the
parser. This was the only warning being generated from the higher level
Thrift.Parser code; all other error conditions raise exceptions. This
also lets us remove the now-unused Thrift.Parser.Shell module.
The only downside to moving the warning into the parser is that we lose
a little context (the name of the parent struct), but because we now
include a line number and this is such a rare case, this feels like a
reasonable trade-off.
This change extracts line numbers from the lexer's tokens and assigns
them to our model structures for later use in error messages or for
debugging.
This change also generalizes model construction by introducing
build_model/2 and build_model/3.
This is a near complete rewrite of our yecc grammar. It includes the
following changes:
1. Our symbols now follow Erlang's TitleCase naming convention. This
makes them easier to differentiate from literals and tokens.
2. We no longer support set constants because they aren't supported by
the Apache Thrift compiler.
3. We no longer support parens around a service definition's "extends"
clause. That was introduced by mistake and isn't part of the official
Thrift IDL specification.
4. We now support both `,` and `;` as list separators. We previously
only supported ',' while ';' was discarded by the tokenizer.
* Connections couldn't be closed
Previously, connections would reconnect if they were closed by calling
`Client.close`. This is bad behavior, and could easily lead to running
out of sockets. `Client.close` now closes the TCP connection and stops
the process.
There are multiple of small changes here that result in a large
simplification of the lexer's rules:
1. Many rules that were mapping literals to atoms have been collapsed in
favor of generic rules that call list_to_atom/1 on TokenChars.
2. Quoted string processing now uses a simpler regular expression paired
with a processing function, giving us support for character escape
sequences.
3. The st_ident token type has been removed because Smalltalk-specific
IDL support was deprecated a long time ago by Apache Thrift and we
don't generate Smalltalk namespaces in our library.
Also, the lexer unit test now verifies that we can tokenize a complete
document.
We generate Elixir exception structs via defexception for Thrift
exception types. That macro always provides an exception/1
implementation, but message/1 is only generated if the exception is
defined with a :message field. When it doesn't, the generated code
produces a warning because the full Exception behaviour hasn't been
implemented.
This change generates a message/1 implementation for all of our Thrift
exception structs which don't have a natural :message field. It returns
the Kernel.inspect/2 string representation of the exception, which is
both simple and unambiguous.
This is signifiant rewrite of the compiler task to support manifests,
making us a better mix citizen and adding support for `mix clean`-based
file cleanup.
Our manifest is written as a binary-encoded Erlang term to give us the
most forward-compatible file format. At the moment, we only store a
manifest version number and the list of generated files, but we may
expand this in the future to include additional dependency information
such as the "version" of the code that was used to generate these files.
See #138 for a more detailed discussion.
We were using size(x) rather than x-signed; apparently, size(x) is for
unsigned numbers.
Fixed, and added unit tests for boundary conditions. Fixes#206