{"id":16752933,"url":"https://github.com/bsutton/rpc_gen","last_synced_at":"2026-02-22T23:35:17.115Z","repository":{"id":141378293,"uuid":"410684171","full_name":"bsutton/rpc_gen","owner":"bsutton","description":"The `rpc_gen` is a builder and generator of RPC (Remote Procedure Call) stub files.","archived":false,"fork":false,"pushed_at":"2021-03-06T05:16:50.000Z","size":96,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-30T23:48:13.290Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bsutton.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-09-26T23:19:10.000Z","updated_at":"2022-02-14T19:41:52.000Z","dependencies_parsed_at":null,"dependency_job_id":"deea59b1-e3f1-4a8c-bc85-a62b13a42ecc","html_url":"https://github.com/bsutton/rpc_gen","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bsutton/rpc_gen","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bsutton%2Frpc_gen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bsutton%2Frpc_gen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bsutton%2Frpc_gen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bsutton%2Frpc_gen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bsutton","download_url":"https://codeload.github.com/bsutton/rpc_gen/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bsutton%2Frpc_gen/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29730785,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-22T20:09:16.275Z","status":"ssl_error","status_checked_at":"2026-02-22T20:09:13.750Z","response_time":110,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-13T02:48:40.146Z","updated_at":"2026-02-22T23:35:17.094Z","avatar_url":"https://github.com/bsutton.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# rpc_gen\n\nThe `rpc_gen` is a builder and generator of RPC (Remote Procedure Call) stub files.  \n\nATTENTION: Under development\n\nVersion 0.1.7\n\nWhy need another RPC?\n\nAlthough this software makes it easy to implement a client and server to call remote procedures (RPC) from the client, it is not limited to this application only.  \n\nThis software also makes it easy to implement a client to access various external APIs in the form of external procedure calls (or simply, method calls).  \n\nIt would be more correct to call this software as RPC without transport (that is, without a specific data format and methods for sending and receiving data).  \n\nHalf of the work (or even most) will be done by this software.  \nThe rest of the work will be done by transport (sending and receiving data).  \nThe transport is not only concerned with the delivery of data, but it also determines how and in what format the data will be sent.  \n\nThis makes it possible (using transport) to follow many conventions and makes it possible to emulate the client calls in a different formats (e.g. RestFul).  \n\nOf course, all of this can be done manually, but it may take a little longer.  \n\nThe main goals and purpose of this project:\n\n- Easy adaptation for use with existing external APIs using your own transport\n- Fast (in minutes) creation of API services simultaneously for server and client\n- Fast (in seconds) creation of API procedures simultaneously on the server and client\n- Maximum flexibility in the choice of how to send and handle procedure calls\n- Easily maintenance of consistency of source code for procedures on both layers\n\nConventions (or limitations, if you like to call it that):\n\n- The JSON models must contain a constructor like `fromJson(Map\u003cString, dynamic\u003e json)`\n- The JSON models must contain an instance method like `Map\u003cString, dynamic\u003e toJson()`\n- The RPC service metadata class (interface) should contain nothing but methods\n\nPlanned features:\n\n- Applying automatic default values when deserializing non-nullable types (soon)  \n- Implementing automatic  JSON serialization/deserialization of plain objects (not soon)  \n\nExample of procedures:\n\n```dart\n@RpcMethod(path: '/ping')\nFuture\u003cvoid\u003e ping();\n\n@RpcMethod(path: '/add')\nFuture\u003cint\u003e add({int x, int y});\n\n@RpcMethod(path: '/comments', httpMethod: 'GET', ignoreIfNull: true)\nFuture\u003cList\u003cComment\u003e\u003e comments({String? email, int? id, int? postId});\n\n@RpcMethod(path: '/find_products')\nFuture\u003cList\u003cProduct\u003e\u003e findProducts({List\u003cString\u003e? q, int? page, int? pageSize});\n\n@RpcMethod(path: '/find_products')\nFuture\u003cTResponse\u003e findProducts(TRequest request);\n```\n\nA very simple example:\n\nUsing free fake API for testing and prototyping '{JSON} Placeholder' at https://jsonplaceholder.typicode.com.\n\nService declaration:\n\n```dart\n@RpcService(host: 'https://jsonplaceholder.typicode.com')\nabstract class JsonPlaceholder {\n  @RpcMethod(path: '/posts', httpMethod: 'POST')\n  Future\u003cPost\u003e addPost(Post post);\n\n  @RpcMethod(path: '/comments', httpMethod: 'GET', ignoreIfNull: true)\n  Future\u003cList\u003cComment\u003e\u003e comments({String? email, int? id, int? postId});\n\n  @RpcMethod(path: '/posts/\u003cid\u003e/comments', httpMethod: 'GET')\n  Future\u003cList\u003cComment\u003e\u003e commentsByPostId(int id);\n\n  @RpcMethod(path: '/posts/\u003cid\u003e', httpMethod: 'GET')\n  Future\u003cPost\u003e postById(int id);\n\n  @RpcMethod(path: '/posts/\u003cid\u003e', httpMethod: 'PUT')\n  Future\u003cPost\u003e updatePost(Post post, {required int id});\n}\n\n```\n\nSource code of example:\n\n[example_use_jsonplaceholder.dart](https://github.com/mezoni/rpc_gen/blob/main/example/example_use_jsonplaceholder.dart)\n\n```dart\nimport 'dart:convert';\n\nimport 'package:http/http.dart' as _http;\nimport 'package:json_annotation/json_annotation.dart';\nimport 'package:rpc_gen/rpc_meta.dart';\n\nimport '_rest_transport.dart';\n\npart 'example_use_jsonplaceholder.g.dart';\n\nFuture\u003cvoid\u003e main(List\u003cString\u003e args) async {\n  final client = Client();\n  final commnetId = 1;\n  print('Comments with id $commnetId:');\n  final commentsById = await client.comments(id: commnetId);\n  for (final comment in commentsById) {\n    print('id: ${comment.id}, email: ${comment.email}');\n  }\n\n  print('====');\n  if (commentsById.isNotEmpty) {\n    final comment = commentsById.first;\n    print('Comments with email ${comment.email}:');\n    final commentsByEmail = await client.comments(email: comment.email);\n    for (final comment in commentsByEmail) {\n      print('id: ${comment.id}, email: ${comment.email}');\n    }\n  }\n\n  print('====');\n  final postId = 1;\n  var post = await client.postById(postId);\n  print('Post with id $postId:');\n  print('id: ${post.id}, user id ${post.userId}');\n\n  print('====');\n  post = Post()\n    ..title = 'foo'\n    ..body = 'bar'\n    ..userId = 1;\n\n  try {\n    post = await client.addPost(post);\n    print('Added post with id ${post.id}');\n  } catch (e) {\n    print(e);\n  }\n\n  print('====');\n  try {\n    post.id = 1;\n    post.title = 'it works!';\n    post = await client.updatePost(post, id: 1);\n    print('Update post with id ${post.id}, title: ${post.title}');\n  } catch (e) {\n    print(e);\n  }\n}\n\nclass Client extends JsonPlaceholderClient {\n  Client() : super(Transport(host: JsonPlaceholderConfig.host));\n}\n\n@JsonSerializable()\nclass Comment {\n  String? body;\n  String? email;\n  int? id;\n  int? postId;\n\n  Comment();\n\n  factory Comment.fromJson(Map\u003cString, dynamic\u003e json) =\u003e\n      _$CommentFromJson(json);\n\n  Map\u003cString, dynamic\u003e toJson() =\u003e _$CommentToJson(this);\n}\n\n@RpcService(host: 'https://jsonplaceholder.typicode.com')\nabstract class JsonPlaceholder {\n  @RpcMethod(path: '/posts', httpMethod: 'POST')\n  Future\u003cPost\u003e addPost(Post post);\n\n  @RpcMethod(path: '/comments', httpMethod: 'GET', ignoreIfNull: true)\n  Future\u003cList\u003cComment\u003e\u003e comments({String? email, int? id, int? postId});\n\n  @RpcMethod(path: '/posts/\u003cid\u003e/comments', httpMethod: 'GET')\n  Future\u003cList\u003cComment\u003e\u003e commentsByPostId(int id);\n\n  @RpcMethod(path: '/posts/\u003cid\u003e', httpMethod: 'GET')\n  Future\u003cPost\u003e postById(int id);\n\n  @RpcMethod(path: '/posts/\u003cid\u003e', httpMethod: 'PUT')\n  Future\u003cPost\u003e updatePost(Post post, {required int id});\n}\n\n@JsonSerializable()\nclass Post {\n  int? userId;\n  int? id;\n  String? title;\n  String? body;\n\n  Post();\n\n  factory Post.fromJson(Map\u003cString, dynamic\u003e json) =\u003e _$PostFromJson(json);\n\n  Map\u003cString, dynamic\u003e toJson() =\u003e _$PostToJson(this);\n}\n\nclass Transport extends RestTransport with JsonPlaceholderTransport {\n  @override\n  final String host;\n\n  @override\n  final int? port;\n\n  Transport({required this.host, this.port});\n\n  @override\n  Future postprocess(RpcRequest request, _http.Response response) async {\n    switch (response.statusCode) {\n      case 200:\n      case 201:\n        return jsonDecode(response.body);\n      default:\n        throw 'Bad response: ${response.statusCode}\\n${response.body}';\n    }\n  }\n}\n\n```\n\nAnother example:\n\nLet's say we need to organize and coordinate the call of the following remote procedures:\n\n- `Future\u003cint\u003e add({required int x, required int y})`\n\nHow can we quickly organize client-server interactions?  \nWhat if we need not 3 procedures, but much more?  \n\nHow to implement it quickly and conveniently and without errors?  \n\n[example.dart](https://github.com/mezoni/rpc_gen/blob/main/example/example.dart)\n\n```dart\nimport 'dart:async';\nimport 'dart:convert';\nimport 'dart:io';\n\nimport 'package:http/src/response.dart';\nimport 'package:rpc_gen/rpc_meta.dart';\n\nimport '_http_transport.dart';\n\npart 'example.g.dart';\n\nvoid main() async {\n  // Schedule a delayed client launch\n  Timer(Duration(seconds: 2), () async {\n    await _runCleint();\n    exit(0);\n  });\n\n  // Start web server\n  await _serve();\n}\n\nconst secretHeaderKey = 'SECRET';\n\nconst secretToken = '123';\n\nbool development = true;\n\nString? globalSecret;\n\n/// Client runner, for demonstration only\nFuture\u003cvoid\u003e _runCleint() async {\n  final client = Client();\n  globalSecret = secretToken;\n  final x = 2;\n  final y = 3;\n  final res1 = await client.add(x: x, y: y);\n  print('add($x,$y) = $res1');\n}\n\n/// Web server, for demonstration only\nFuture\u003cvoid\u003e _serve() async {\n  final webServer = await HttpServer.bind(\n      InternetAddress.anyIPv4, ExampleApiConfig.serverPort!);\n  await for (final request in webServer) {\n    final response = request.response;\n    try {\n      final path = request.uri.path;\n      final methods = ExampleApiUtils.getMethods()\n          .where((e) =\u003e e.path == path \u0026\u0026 e.method == request.method);\n      if (methods.isNotEmpty) {\n        final handler = ServerHandler();\n        final method = methods.first;\n        if (method.authorize) {\n          if (request.headers.value(secretHeaderKey) != secretToken) {\n            response.statusCode = 401;\n            throw 'Unauthorized access';\n          } else {\n            print('Authorized access');\n          }\n        }\n\n        final source = await utf8.decodeStream(request);\n        final data = jsonDecode(source);\n        Map\u003cString, dynamic\u003e? positionalArguments;\n        Map\u003cString, dynamic\u003e? namedArguments;\n        if (data is Map) {\n          final p = data['p'];\n          if (p is Map\u003cString, dynamic\u003e) {\n            positionalArguments = p;\n          }\n\n          final n = data['n'];\n          if (n is Map\u003cString, dynamic\u003e) {\n            namedArguments = n;\n          }\n        }\n\n        if (positionalArguments == null || namedArguments == null) {\n          throw StateError('Invalid data format');\n        }\n\n        final result = await handler.handle(\n            method.name, positionalArguments, namedArguments);\n        response.headers.add('Content-Type', 'application/json');\n        response.write(jsonEncode(result));\n      } else {\n        response.statusCode = 404;\n      }\n    } catch (e) {\n      print(e);\n    } finally {\n      await response.close();\n    }\n  }\n}\n\nclass Client extends ExampleApiClient {\n  Client() : super(ClientTransport());\n}\n\nclass ClientTransport extends Transport with ExampleApiTransport {\n  ClientTransport()\n      : super(host: ExampleApiConfig.host, port: ExampleApiConfig.clientPort);\n}\n\n@RpcService(host: 'http://exmaple.com', serverPort: 8002)\nabstract class ExampleApi {\n  @RpcMethod(path: '/api/v1/add', authorize: true)\n  Future\u003cint\u003e add({required int x, required int y});\n}\n\nclass ServerHandler extends ExampleApiHandler {\n  ServerHandler() : super(ServerService());\n}\n\nclass ServerService extends ExampleApi {\n  @override\n  Future\u003cint\u003e add({required int x, required int y}) async {\n    return x + y;\n  }\n}\n\nclass Transport extends HttpTransport with ExampleApiTransport {\n  @override\n  final String host;\n\n  @override\n  final int? port;\n\n  Transport({required String host, this.port})\n      : host = development ? 'http://localhost' : host;\n\n  @override\n  Future postprocess(RpcRequest request, Response response) async {\n    switch (response.statusCode) {\n      case 200:\n        return jsonDecode(response.body);\n      default:\n        throw StateError(\n            'Bad response: ${response.statusCode}\\n${response.body}');\n    }\n  }\n\n  @override\n  Future\u003cRpcRequest\u003e preprocess(\n      String name,\n      String httpMethod,\n      String path,\n      Map\u003cString, dynamic\u003e positionalArguments,\n      Map\u003cString, dynamic\u003e namedArguments) async {\n    final body = {'p': positionalArguments, 'n': namedArguments};\n    final url =\n        port == null ? Uri.parse('$host$path') : Uri.parse('$host:$port$path');\n    final request = RpcRequest(url: url, body: body);\n    request.headers['Content-Type'] = 'application/json';\n    if (globalSecret != null) {\n      request.headers[secretHeaderKey] = secretToken;\n    }\n\n    return request;\n  }\n}\n\n```\n\nBased on this code (in fact, interfaces), classes for work on the server and client will be generated.\n\nList of generated classes:\n\n- ExampleApiClient\n- ExampleApiHandler\n- ExampleApiMethod\n- ExampleApiTransport\n- ExampleApiUtils\n\nAll of these classes contain stubs for client and server procedures.  \n\nAn interface is also generated for organizing the sending of calls from the client to the server (the so-called transport). You write yourself the implementation you need and, in particular, you can create a universal base transport class if you need to work with different services (API).\n\nAnd, of course, for the convenience of processing on the server, convenient metadata classes for the procedures used have been created. This gives maximum flexibility in the choice of processing methods. They are also available on the client and can be used in transport class (and not only there) if you need it.\n\nA utility class is also generated. At the moment it only allows getting a list of procedure metadata.\n\nSo it's time to take a look at the generated classes (there is not so much source code).  \n\n[example.g.dart](https://github.com/mezoni/rpc_gen/blob/main/example/example.g.dart)\n\n```dart\n// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'example.dart';\n\n// **************************************************************************\n// RpcGenerator\n// **************************************************************************\n\nclass ExampleApiClient implements ExampleApi {\n  ExampleApiClient(this._transport);\n\n  final ExampleApiTransport _transport;\n\n  @override\n  Future\u003cint\u003e add({required int x, required int y}) async {\n    final $1 = \u003cString, dynamic\u003e{};\n    final $2 = \u003cString, dynamic\u003e{};\n    $2['x'] = x;\n    $2['y'] = y;\n    final $0 = await _transport.send('add', 'POST', '/api/v1/add', $1, $2);\n    return $0 as int;\n  }\n}\n\nclass ExampleApiConfig {\n  static const int? clientPort = 8002;\n\n  static const String host = 'http://exmaple.com';\n\n  static const int? serverPort = 8002;\n}\n\nclass ExampleApiHandler {\n  ExampleApiHandler(this._handler);\n\n  final ExampleApi _handler;\n\n  Future handle(String name, Map\u003cString, dynamic\u003e positionalArguments,\n      Map\u003cString, dynamic\u003e namedArguments) async {\n    switch (name) {\n      case 'add':\n        final $0 = namedArguments['x'] as int;\n        final $1 = namedArguments['y'] as int;\n        final $2 = await _handler.add(x: $0, y: $1);\n        return $2;\n      default:\n        throw StateError('Unknown remote procedure: \\'$name\\'');\n    }\n  }\n}\n\nclass ExampleApiMethod {\n  const ExampleApiMethod(\n      {required this.authorize,\n      required this.method,\n      required this.name,\n      required this.namedParameters,\n      required this.positionalParameters,\n      required this.path});\n\n  final bool authorize;\n\n  final String method;\n\n  final String name;\n\n  final List\u003cString\u003e namedParameters;\n\n  final List\u003cString\u003e positionalParameters;\n\n  final String path;\n}\n\nabstract class ExampleApiTransport {\n  Future send(\n      String name,\n      String httpMethod,\n      String path,\n      Map\u003cString, dynamic\u003e positionalArguments,\n      Map\u003cString, dynamic\u003e namedArguments);\n}\n\nabstract class ExampleApiUtils {\n  static List\u003cExampleApiMethod\u003e getMethods() {\n    return const [\n      ExampleApiMethod(\n          authorize: true,\n          method: 'POST',\n          name: 'add',\n          namedParameters: [],\n          path: '/api/v1/add',\n          positionalParameters: [])\n    ];\n  }\n}\n\n```\n\nThat's all for now!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbsutton%2Frpc_gen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbsutton%2Frpc_gen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbsutton%2Frpc_gen/lists"}