Skip to content

Commit 618a557

Browse files
authored
Merge pull request #90 from seriyps/idl-parser
Idl parser
2 parents f7dbd29 + ee94d26 commit 618a557

22 files changed

+1619
-17
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ out/
1212
_build
1313
rebar.lock
1414
*.crashdump
15+
src/avro_idl_lexer.erl
16+
src/avro_idl_parser.erl

README.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,106 @@ Because we believe the use case of tagged unions in decoder output is not as com
242242
You may use the decoder hook `avro_decoer_hooks:tag_unions/0` to have the decoded values tagged.
243243
NOTE: only named complex types are tagged by this hook, you can of course write your own hook for a different tagging behaviour.
244244

245+
# Avro IDL (.avdl) Support
246+
247+
erlavro can parse [Avro IDL](https://avro.apache.org/docs/current/idl-language/)
248+
files (`.avdl`) — the human-readable schema definition language — and convert
249+
them to Avro schemas for encoding and decoding.
250+
251+
## Loading an .avdl schema file
252+
253+
Given a schema file `com/example/my_service.avdl`:
254+
255+
```avdl
256+
@namespace("com.example")
257+
protocol MyService {
258+
enum Status { OK, ERROR }
259+
260+
record Response {
261+
string id;
262+
Status status;
263+
union { null, string } message = null;
264+
}
265+
266+
Response process(string id);
267+
}
268+
```
269+
270+
Load it into a schema store and use it for encoding/decoding:
271+
272+
```erlang
273+
%% Load all types from the .avdl file into a schema store
274+
Store = avro_schema_store:new([], ["com/example/my_service.avdl"]),
275+
276+
%% Look up a type by its full name and make an encoder/decoder
277+
LookupFun = avro_schema_store:to_lookup_fun(Store),
278+
Encoder = avro:make_encoder(LookupFun, []),
279+
Decoder = avro:make_decoder(LookupFun, []),
280+
281+
Record = [{"id", <<"req-1">>}, {"status", <<"OK">>}, {"message", null}],
282+
Bin = iolist_to_binary(Encoder("com.example.Response", Record)),
283+
Record = Decoder("com.example.Response", Bin).
284+
```
285+
286+
## Importing types across .avdl files
287+
288+
IDL files can import types from other files using `import idl`, `import schema`
289+
(`.avsc`), or `import protocol` (`.avpr`) statements. Import paths are resolved
290+
relative to the importing file's directory, so nested imports work correctly.
291+
292+
```avdl
293+
@namespace("com.example")
294+
protocol Orders {
295+
import idl "common/types.avdl"; %% imports Common.Address, Common.Money
296+
import schema "status.avsc"; %% imports a plain JSON schema
297+
298+
record Order {
299+
string id;
300+
com.example.common.Address shipping_address;
301+
com.example.common.Money total;
302+
}
303+
}
304+
```
305+
306+
```erlang
307+
Store = avro_schema_store:new([], ["schemas/orders.avdl"]),
308+
```
309+
310+
## In-memory schema loading (no filesystem)
311+
312+
For testing or embedded schemas, supply a custom `read_fun` that resolves
313+
import paths from memory instead of the filesystem:
314+
315+
```erlang
316+
Files = #{
317+
{"schemas", "common.avdl"} =>
318+
<<"protocol Common { record Address { string city; } }">>,
319+
{"schemas", "orders.avdl"} =>
320+
<<"protocol Orders {\n"
321+
" import idl \"common.avdl\";\n"
322+
" record Order { string id; Address addr; }\n"
323+
"}">>
324+
},
325+
ReadFun = fun(Cwd, Path) ->
326+
case maps:find({Cwd, Path}, Files) of
327+
{ok, Bin} -> {ok, Bin};
328+
error -> {error, enoent}
329+
end
330+
end,
331+
{ok, Bin} = maps:find({"schemas", "orders.avdl"}, Files),
332+
Schema = avro_idl:decode_schema(
333+
binary_to_list(Bin), "schemas",
334+
[{read_fun, ReadFun}]).
335+
```
336+
337+
## Converting .avdl to AVPR (JSON protocol)
338+
339+
```erlang
340+
{ok, Bin} = file:read_file("my_service.avdl"),
341+
Avpr = avro_idl:str_to_avpr(binary_to_list(Bin), filename:dirname("my_service.avdl")),
342+
io:format("~s~n", [jsone:encode(Avpr)]).
343+
```
344+
245345
# Object container file encoding/decoding
246346

247347
See `avro_ocf.erl` for details

include/erlavro.hrl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
-define(AVRO_MAP, <<"map">>).
3838
-define(AVRO_UNION, <<"union">>).
3939
-define(AVRO_FIXED, <<"fixed">>).
40+
-define(AVRO_ERROR, <<"error">>). % idl
4041

4142
-define(IS_AVRO_PRIMITIVE_NAME(N),
4243
(N =:= ?AVRO_NULL orelse

rebar.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@
2323
{cover_export_enabled, true}.
2424

2525
{dialyzer, [{warnings, [unknown]}]}.
26+
{yrl_opts, [{verbose, true}]}.

src/avro.erl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ set_json_provider(Module) -> avro_json_compat:set_provider(Module).
154154
get_json_provider() -> avro_json_compat:get_provider().
155155

156156
%% @doc Decode JSON format avro schema into `erlavro' internals.
157-
-spec decode_schema(binary()) -> avro_type().
157+
%% @param JSON: JSON binary or erlang `map()' json representation
158+
-spec decode_schema(binary() | map() | [map()]) -> avro_type().
158159
decode_schema(JSON) -> avro_json_decoder:decode_schema(JSON).
159160

160161
%% @doc Make type lookup function from type definition.
@@ -187,7 +188,8 @@ make_lkup_fun(AssignedName, Type) ->
187188
%% * allow_type_redefine: `boolean()'
188189
%% This option is to allow one type being defined more than once.
189190
%% @end
190-
-spec decode_schema(binary(), proplists:proplist()) -> avro_type().
191+
-spec decode_schema(binary() | map() | [map()], proplists:proplist()) ->
192+
avro_type().
191193
decode_schema(JSON, Options) ->
192194
avro_json_decoder:decode_schema(JSON, Options).
193195

@@ -280,7 +282,8 @@ make_decoder(Schema, Options) ->
280282
%% takes only one `binary()' input arg.
281283
-spec make_simple_decoder(avro_type() | binary(), codec_options()) ->
282284
simple_decoder().
283-
make_simple_decoder(JSON, Options) when is_binary(JSON) ->
285+
make_simple_decoder(JSON, Options) when is_binary(JSON);
286+
is_map(JSON) ->
284287
make_simple_decoder(decode_schema(JSON), Options);
285288
make_simple_decoder(Type, Options) when ?IS_TYPE_RECORD(Type) ->
286289
Lkup = make_lkup_fun(Type),

0 commit comments

Comments
 (0)