At the beginning of last year, I had the idea to write my own interface language (IDL), which would be similar to Protobuf or Thrift, but would be intended for the web. I was hoping to finish it somewhere in three months. A little more than a year passed before the first stable version.
github.user(1).repos().all()
. message Human { id int64; name string; birthday datetime; sex Sex; continent ContinentName; }
{ "id": 1, "name": "Ivan Korobkov", "birthday": "1987-08-07T00:00Z", "sex": "male", "continent": "europe" }
Human human = new Human() .setId(1) .setName("John") .setSex(Sex.MALE) .setContinent(ContinentName.ASIA) String json = human.toJson(); Human another = Human.fromJson(json);
human = Human(id=1, name="John") human.birthday = datetime.datetime(1900, 1, 2) s = human.to_json() another = Human.from_json(s)
Human *human = [[Human alloc]init]; human.id = 1; human.name = @"John"; human.sex = Sex_MALE; human.continent = ContinentName_EUROPE; NSError *error = nil; NSData *data = [human toJsonError:&error]; Human *another = [Human messageWithData:data error:&error];
pip install pdef-compiler
python setup.py install
pip install pdef-java pip install pdef-python pip install pdef-objc
pdefc -v check https://raw.github.com/pdef/pdef/master/example/world.yaml
pdefc -h ... generate-python Python code generator. generate-objc Objective-C code generator. generate-java Java code generator. pdefc generate-python -h
myproject.yaml
: package: name: myproject modules: - posts - photos
// posts.pdef namespace myproject; import myproject.photos; interface Posts { get(id int64) Post; @post create(title string @post, text string @post) Post; } message Post { id int64; title string; text string; photos list<Photo>; }
// photos.pdef namespace myproject; message Photo { id int64; url string; }
pdefc generate-java myproject.yaml --out generated-java/ pdefc generate-python myproject.yaml --out generated-python/ pdefc generate-objc myproject.yaml --out generated-objc/
namespace example; interface MyInterface { method( arg0 int32, arg1 string ) list<string>; } message MyMessage { field0 int32; field1 string; } enum Number { ONE, TWO; }
/** * This is a multi-line module docstring. * It is a available to the code generators. * * Start each line with a star because it is used * as line whitespace/text delimiter when * the docstring is indented (as method docstrings). */ namespace example; // This is a one line comment, it is stripped from the source code. /** Interface docstring. */ interface ExampleInterface { /** * Method docstring. */ hello(name string) string; }
yaml
file, which contains the name of the package and lists the modules and dependencies. Cyclic dependencies between packages are prohibited. Module names are automatically mapped to files. For this, the points are replaced with the system directory separator and the extension .pdef
. For example, users.events
corresponds to the users/events.pdef
. Dependencies indicate the package name and an optional path to its yaml
file yaml
a space. Dependency paths can be set and overridden when executing console commands. package: # Package name name: example # Additional information version: 1.1 url: https://github.com/pdef/pdef/ author: Ivan Korobkov <ivan.korobkov@gmail.com> description: Example application # Module files. modules: - example - photos - users - users.profile # Other packages this package depends on. dependencies: - common - pdef_test https://raw.github.com/pdef/pdef/1.1/test/test.yaml
api
directory is optional): api/ example.yaml example.pdef photos.pdef users.pdef users/profile.pdef
*.pdef
file with a description of messages, interfaces, and enumerations. Each module immediately after the optional documentation should contain an indication of the namespace. All types in the same namespace must have unique names. Different packages can use the same namespace.twitter
, github
, etc. /** Module with a namespace. */ namespace myproject; message Hello { text string; }
include
in other languages, they allow you to access types from another module in one module. Imports are placed immediately after specifying the module namespace. Modules are imported using the package name and file path without the .pdef
extension, with a .pdef
instead of a directory separator. When the module name matches the package name, the module can be imported only by the package name. namespace example; import package; // Equivalent to "import package.package" when package/module names match. import package.module;
namespace example; from package.module import submodule0, submodule1;
// users.pdef namespace example; from example import photos; // Circular import. message User { bestFriend User; // References a declaring type. photo Photo; // References a type from another module. }
// photos.pdef namespace example; from example import users; // Circular import. message Photo { user User; // References a user from another module. }
MyMessage
; within a different namespace.MyMessag
full name is namespace.MyMessag
.void
is a special type that indicates that the method does not return a result.bool
: boolean value (true / false)int16
: signed 16-bit numberint32
: a significant 32-bit numberint64
: signed 64-bit numberfloat
: 32-bit floating point numberdouble
: 64-bit floating point numberstring
: Unicode stringdatetime
: date to time without specifying the time zonelist
: an ordered list whose elements can be any data type.set
: an unordered set of unique values, the elements of which can be any data type.map
: container is key-value, keys must be primitive types, values can be any data types. message Containers { numbers list<int32>; tweets list<Tweet>; ids set<int64>; colors set<Color>; userNames map<int64, string>; photos map<string, list<Photo>>; }
enum Sex { MALE, FEMALE; } enum EventType { USER_REGISTERED, USER_BANNED, PHOTO_UPLOADED, PHOTO_DELETED, MESSAGE_RECEIVED; }
struct
'a) is a collection of statically typed named fields. Messages support simple and polymorphic inheritance. Messages defined as exceptions can optionally be used to specify exceptions in interest. /** Example message. */ message User { id int64; name string; age int32; profile Profile; friends set<User>; // Self-referencing. } /** Example exception. */ exception UserNotFound { userId int64; }
message EditableUser { name string; sex Sex; birthday datetime; } message User : EditableUser { id int32; lastSeen datetime; friendsCount int32; likesCount int32; photosCount int32; } message UserWithDetails : User { photos list<Photo>; friends list<User>; }
@discriminator
.message Subtype : Base(DiscriminatorEnum.VALUE)
. /** Discriminator enum. */ enum EventType { USER_EVENT, USER_REGISTERED, USER_BANNED, PHOTO_UPLOADED, } /** Base event with a discriminator field. */ message Event { type EventType @discriminator; // The type field marked as @discriminator time datetime; } /** Base user event. */ message UserEvent : Event(EventType.USER_EVENT) { user User; } message UserRegistered : UserEvent(EventType.USER_REGISTERED) { ip string; browser string; device string; } message UserBanned : UserEvent(EventType.USER_BANNED) { moderatorId int64; reason string; } message PhotoUploaded : Event(EventType.PHOTO_UPLOADED) { photo Photo; userId int64; }
void
. The method is called interface when it returns the interface. A sequential method call must end with a terminal method, for example, app.users().register("John Doe")
.@post
to separate methods that modify data. Their arguments can also be marked as @post
. HTTP RPC sends these methods as POST requests, and @post
adds arguments to the request body.@post
can have @query
arguments that are sent as an HTTP query string.@post
.@post
can have @query
arguments. interface Application { /** Void method. */ void0() void; /** Interface method. */ service(arg int32) Service; /** Method with 3 args. */ method(arg0 int32, arg1 string, arg2 list<string>) string; } interface Service { /** Terminal method with @query args. */ query(limit int32 @query, offset int32 @query) list<string>; /** Terminal post method with one of args marked as @post. */ @post mutator(arg0 int32, postArg string @post) string; }
interface BaseInterface { method() void; } interface SubInterface : BaseInterface { anotherMethod() void; }
@throws(Exception)
. The root interface is the interface from which all calls begin. Exceptions to other interfaces in the call chain are ignored. To support multiple exceptions, polymorphic inheritance or composition is used. Usually there is one root interface, for example, Github
or Twitter
, and one exception. @throws(AppException) interface Application { users() Users; photos() Photos; search() Search; } enum AppExceptionCode { AUTH_EXC, VALIDATION_EXC, FORBIDDEN_EXC } exception AppException { type AppExceptionCode @discriminator; } exception AuthExc : AppException(AppExceptionCode.AUTH_EXC) {} exception ValidationExc : AppException(AppExceptionCode.VALIDATION_EXC) {} exception ForbiddenExc : AppException(AppExceptionCode.FORBIDDEN_EXC) {}
native mytype
), weak typing (when the field or the result of the method was of object
type, and had to unpack it themselves), and much more. As a result, I hope, we have a simple, easy-to-read and easy-to-use language.Source: https://habr.com/ru/post/209018/
All Articles