2009-10-19 20:36:42 +08:00
|
|
|
# Protocol extensions
|
|
|
|
|
2010-05-23 18:56:49 +08:00
|
|
|
The `amqp_codegen.py` AMQP specification compiler has recently been
|
|
|
|
enhanced to take more than a single specification file, which allows
|
|
|
|
AMQP library authors to include extensions to the core protocol
|
|
|
|
without needing to modify the core AMQP specification file as
|
|
|
|
distributed.
|
|
|
|
|
|
|
|
The compiler is invoked with the path to a single "main" specification
|
|
|
|
document and zero or more paths to "extension" documents.
|
|
|
|
|
|
|
|
The order of the extensions matters: any later class property
|
|
|
|
definitions, for instance, are added to the list of definitions in
|
|
|
|
order of appearance. In general, composition of extensions with a core
|
|
|
|
specification document is therefore non-commutative.
|
|
|
|
|
|
|
|
## The main document
|
|
|
|
|
|
|
|
Written in the style of a
|
2019-03-20 16:13:25 +08:00
|
|
|
[json-shapes](https://github.com/tonyg/json-shapes) schema:
|
2010-05-23 18:56:49 +08:00
|
|
|
|
|
|
|
DomainDefinition = _and(array_of(string()), array_length_equals(2));
|
|
|
|
|
|
|
|
ConstantDefinition = {
|
|
|
|
"name": string(),
|
2010-05-23 19:11:06 +08:00
|
|
|
"value": number(),
|
2010-05-23 18:56:49 +08:00
|
|
|
"class": optional(_or("soft-error", "hard-error"))
|
|
|
|
};
|
|
|
|
|
|
|
|
FieldDefinition = {
|
|
|
|
"name": string(),
|
|
|
|
"type": string(),
|
|
|
|
"default-value": optional(anything())
|
|
|
|
};
|
|
|
|
|
|
|
|
MethodDefinition = {
|
|
|
|
"name": string(),
|
|
|
|
"id": number(),
|
|
|
|
"arguments": array_of(FieldDefinition),
|
2010-05-23 19:11:06 +08:00
|
|
|
"synchronous": optional(boolean()),
|
|
|
|
"content": optional(boolean())
|
2010-05-23 18:56:49 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
ClassDefinition = {
|
|
|
|
"name": string(),
|
|
|
|
"id": number(),
|
|
|
|
"methods": array_of(MethodDefinition),
|
|
|
|
"properties": optional(array_of(FieldDefinition))
|
|
|
|
};
|
|
|
|
|
|
|
|
MainDocument = {
|
|
|
|
"major-version": number(),
|
|
|
|
"minor-version": number(),
|
|
|
|
"revision": optional(number()),
|
|
|
|
"port": number(),
|
|
|
|
"domains": array_of(DomainDefinition),
|
|
|
|
"constants": array_of(ConstantDefinition),
|
|
|
|
"classes": array_of(ClassDefinition),
|
|
|
|
}
|
|
|
|
|
|
|
|
Within a `FieldDefinition`, the keyword `domain` can be used instead
|
|
|
|
of `type`, but `type` is preferred and `domain` is deprecated.
|
|
|
|
|
2010-05-23 19:11:06 +08:00
|
|
|
Type names can either be a defined `domain` name or a built-in name
|
|
|
|
from the following list:
|
|
|
|
|
|
|
|
- octet
|
|
|
|
- shortstr
|
|
|
|
- longstr
|
|
|
|
- short
|
|
|
|
- long
|
|
|
|
- longlong
|
|
|
|
- bit
|
|
|
|
- table
|
|
|
|
- timestamp
|
|
|
|
|
|
|
|
Method and class IDs must be integers between 0 and 65535,
|
|
|
|
inclusive. Note that there is no specific subset of the space reserved
|
|
|
|
for experimental or site-local extensions, so be careful not to
|
|
|
|
conflict with IDs used by the AMQP core specification.
|
|
|
|
|
|
|
|
If the `synchronous` field of a `MethodDefinition` is missing, it is
|
|
|
|
assumed to be `false`; the same applies to the `content` field.
|
|
|
|
|
|
|
|
A `ConstantDefinition` with a `class` attribute is considered to be an
|
|
|
|
error-code definition; otherwise, it is considered to be a
|
|
|
|
straightforward numeric constant.
|
|
|
|
|
2010-05-23 18:56:49 +08:00
|
|
|
## Extensions
|
|
|
|
|
|
|
|
Written in the style of a
|
2019-03-20 16:13:25 +08:00
|
|
|
[json-shapes](https://github.com/tonyg/json-shapes) schema, and
|
2010-05-23 18:56:49 +08:00
|
|
|
referencing some of the type definitions given above:
|
|
|
|
|
|
|
|
ExtensionDocument = {
|
|
|
|
"extension": anything(),
|
|
|
|
"domains": array_of(DomainDefinition),
|
|
|
|
"constants": array_of(ConstantDefinition),
|
|
|
|
"classes": array_of(ClassDefinition)
|
|
|
|
};
|
|
|
|
|
|
|
|
The `extension` keyword is used to describe the extension informally
|
|
|
|
for human readers. Typically it will be a dictionary, with members
|
|
|
|
such as:
|
|
|
|
|
|
|
|
{
|
|
|
|
"name": "The name of the extension",
|
|
|
|
"version": "1.0",
|
|
|
|
"copyright": "Copyright (C) 1234 Yoyodyne, Inc."
|
|
|
|
}
|
|
|
|
|
|
|
|
## Merge behaviour
|
|
|
|
|
|
|
|
In the case of conflicts between values specified in the main document
|
|
|
|
and in any extension documents, type-specific merge operators are
|
|
|
|
invoked.
|
|
|
|
|
|
|
|
- Any doubly-defined domain names are regarded as true
|
|
|
|
conflicts. Otherwise, all the domain definitions from all the main
|
|
|
|
and extension documents supplied to the compiler are merged into a
|
|
|
|
single dictionary.
|
|
|
|
|
|
|
|
- Constant definitions are treated as per domain names above,
|
|
|
|
*mutatis mutandis*.
|
|
|
|
|
|
|
|
- Classes and their methods are a little trickier: if an extension
|
|
|
|
defines a class with the same name as one previously defined, then
|
|
|
|
only the `methods` and `properties` fields of the extension's class
|
|
|
|
definition are attended to.
|
|
|
|
|
|
|
|
- Any doubly-defined method names or property names within a class
|
|
|
|
are treated as true conflicts.
|
|
|
|
|
|
|
|
- Properties defined in an extension are added to the end of the
|
|
|
|
extant property list for the class.
|
|
|
|
|
|
|
|
(Extensions are of course permitted to define brand new classes as
|
|
|
|
well as to extend existing ones.)
|
|
|
|
|
|
|
|
- Any other kind of conflict leads to a raised
|
|
|
|
`AmqpSpecFileMergeConflict` exception.
|
|
|
|
|
|
|
|
## Invoking the spec compiler
|
|
|
|
|
2010-06-11 14:17:14 +08:00
|
|
|
Your code generation code should invoke `amqp_codegen.do_main_dict`
|
|
|
|
with a dictionary of functions as the sole argument. Each will be
|
|
|
|
used for generationg a separate file. The `do_main_dict` function
|
2010-05-23 18:56:49 +08:00
|
|
|
will parse the command-line arguments supplied when python was
|
|
|
|
invoked.
|
|
|
|
|
|
|
|
The command-line will be parsed as:
|
|
|
|
|
2010-06-11 14:17:14 +08:00
|
|
|
python your_codegen.py <action> <mainspec> [<extspec> ...] <outfile>
|
2010-05-23 18:56:49 +08:00
|
|
|
|
2010-06-11 14:17:14 +08:00
|
|
|
where `<action>` is a key into the dictionary supplied to
|
|
|
|
`do_main_dict` and is used to select which generation function is
|
|
|
|
called. The `<mainspec>` and `<extspec>` arguments are file names of
|
|
|
|
specification documents containing expressions in the syntax given
|
|
|
|
above. The *final* argument on the command line, `<outfile>`, is the
|
|
|
|
name of the source-code file to generate.
|
2010-05-23 18:56:49 +08:00
|
|
|
|
|
|
|
Here's a tiny example of the layout of a code generation module that
|
|
|
|
uses `amqp_codegen`:
|
|
|
|
|
|
|
|
import amqp_codegen
|
|
|
|
|
|
|
|
def generateHeader(specPath):
|
|
|
|
spec = amqp_codegen.AmqpSpec(specPath)
|
|
|
|
...
|
|
|
|
|
|
|
|
def generateImpl(specPath):
|
|
|
|
spec = amqp_codegen.AmqpSpec(specPath)
|
|
|
|
...
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2010-06-11 14:17:14 +08:00
|
|
|
amqp_codegen.do_main_dict({"header": generateHeader,
|
|
|
|
"body": generateImpl})
|
2010-05-23 18:56:49 +08:00
|
|
|
|
2010-06-11 14:17:14 +08:00
|
|
|
The reasons for allowing more than one action, are that
|
2010-05-23 18:56:49 +08:00
|
|
|
|
|
|
|
- many languages have separate "header"-type files (C and Erlang, to
|
|
|
|
name two)
|
|
|
|
- `Makefile`s often require separate rules for generating the two
|
|
|
|
kinds of file, but it's convenient to keep the generation code
|
|
|
|
together in a single python module
|
|
|
|
|
|
|
|
The main reason things are laid out this way, however, is simply that
|
|
|
|
it's an accident of the history of the code. We may change the API to
|
2013-07-01 17:49:11 +08:00
|
|
|
`amqp_codegen` in future to clean things up a little.
|