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.
Fixes#253
The problem with the last fix is that generation is recursive. This
means we should be cleaning up each nested context's local lookups when
we move to a deeper context. Otherwise, we're relying on map iteration
order to override a particular value. If the value comes after the one
wer're inserting, we're out of luck.
This PR starts from a known good state with no overrides, and on each
subsequent generation, builds a new resolution map.
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.
This is a significant dialyxir release because it now uses the :dialyzer
module directly instead of spawning a separate VM process in which to
run the dialyzer command line process. More details here:
https://github.com/jeremyjh/dialyxir/wiki/Upgrading-to-0.5
Fortunately for us, we get all of the benefits of this release without
having to make any specific changes to our project configuration.
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.
1. Always refer to models types using their `t` type so we get full
dialyzer visibility into argument and return values.
2. Many field names are atoms now and shouldn't use String.t.
3. Fix the field list and enum list types, which are lists and not maps.
4. Suppress dialyzer warnings in generator/service.ex due to a mismatch
between response field types and function return types.
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 gives us additional control over when to consider existing
artifacts as "stale" and in need of regeneration. At the moment, we
require a strict version string match, but as we mature in accordance
with semver guidelines, we can switch to a more fine-grained approach
based on Version.match?/3.
We don't need to bump the manifest version in this case because ...
1. Reads against existing manifests will safely fail and result in
desirable rebuild behavior.
2. The manifest stuff is brand new, and it's unlike many folks are
using it yet.
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
Up to this point, the only way to specify a schema's namespace was via
the `namespace elixir` Thrift IDL directive. Unfortunately, the Apache
Thrift compiler barks when it encounters "unknown" namespace types. This
also requires all consumers of our library to explicitly add namespace
directives to all of their Thrift IDL files.
This change implements the simplest strategy discussed in #195: a new
top-level `thrift.namespace` configuration option can be set to a global
default namespace. Any schemas without its own `elixir` namespace will
fall back to using this global default value.
The introduction of include_paths made me realize that adding more
parser options (e.g. namespace configuration) would become unwieldy.
This change generalizes parser options as a keyword list with formal
type definitions for supported values. At the moment, :include_paths is
the only option, but it's now much more obvious how to add new ones.
We previously only supported including other Thrift files whose
pathnames were relative to the current file. This change allows a list
of additional include (search) paths to be specified as part of the
project configuration (`thrift.include_paths`) or via a command line
option (`--include dir`).
We now use ThriftTest.thrift file from the Apache Thrift distribution
(original license retained) as a fully representative test file.
We also include StressTest.thrift as an simpler additional test file for
those test cases that work on multiple input files.
Lastly, our tests now clean up generated files `on_exit`. Otherwise,
the last test would always leave its generated files.