protoschema 0.1.9

📐 Programmatically define protobuf contracts using flexible, modular and reusable elements
Documentation
use std::path::Path;

use proto_types::Duration;
use protoschema::{
  common::allow_alias,
  enum_field, enum_map, enum_option, enum_variants, extension, message, message_option, msg_field,
  msg_map, oneof,
  options::{list_value, proto_option},
  packages::Package,
  proto_enum, reusable_fields, services, string, timestamp, uint64,
};

#[test]
fn main_test() -> Result<(), Box<dyn std::error::Error>> {
  let package = Package::new("myapp.v1");
  let file = package.new_file("user");

  let example_option = proto_option(
    "example",
    message_option!(
      "num_list" => list_value([1, 2, 4, 5]),
      "string_list" => list_value(["hello", "there", "general", "kenobi"]),
      "list_of_messages" => list_value([
        message_option!("name" => "cats", "are_cute" => true),
        message_option!("name" => "dogs", "are_cute" => true),
      ]),
    ),
  );

  let test_opts = [
    proto_option("string_opt", "abcde"),
    proto_option(
      "duration_opt",
      Duration {
        seconds: 3600,
        nanos: 0,
      },
    ),
    proto_option("enum_value", enum_option!("UNSPECIFIED")),
    proto_option(
      "message_option",
      message_option!(
        "subject" => "hobbits",
        "location" => "isengard",
      ),
    ),
    example_option.clone(),
  ];

  file.add_options(test_opts.clone());

  extension!(file, MessageOptions {
    1565 => string!("my_custom_message_option"),
  });

  extension!(file, FileOptions {
    1565 => string!("my_custom_file_option"),
  });

  let reusable_variants = enum_variants!(
    imports = [ "my_pkg/reusable/import.proto" ],
    0 => "UNSPECIFIED",
  );

  let reusable_fields = reusable_fields!(
    100 => timestamp!("created_at"),
    101 => timestamp!("updated_at"),
  );

  let user_msg = file.new_message("User");
  let section_msg = file.new_message("Section");
  let subsection_msg = section_msg.new_message("Subsection");
  let sub_subsection_msg = subsection_msg.new_message("SubSubsection");

  let second_package = Package::new("myapp.v2");
  let external_file = second_package.new_file("post");
  let post_msg = external_file.new_message("Post");
  let post_status_enum = external_file.new_enum("post_status");
  let post_category_enum = post_msg.new_enum("post_category");
  let post_metadata_msg = post_msg.new_message("Metadata");

  let user_status_enum = proto_enum!(
    file.new_enum("user_status"),
    options = [ allow_alias() ],
    reserved = [ 405, 200..205 ],
    include(reusable_variants),
    1 => "ACTIVE" { [example_option.clone()] },
    2 => "INACTIVE",
    2 => "PASSIVE"
  );

  let reusable_oneof = oneof!("activity",
    required,
    102 => enum_field!(user_status_enum, "user_current_status"),
    103 => timestamp!("account_deletion_date")
  );

  let referrers_enum = proto_enum!(
    file.new_enum("referrers"),

    include(reusable_variants),

    1 => "GITHUB",
    2 => "REDDIT"
  );

  services!(
    file,
    MyService1 {
      options = [ example_option.clone() ],
      Handler1(user_msg => section_msg) { [ example_option.clone() ] },
      Handler2(post_msg => post_metadata_msg),
    };
  );

  message! {
    user_msg,

    options = [ example_option.clone() ],
    reserved_names = [ "one", "two" ],
    reserved = [ 405, 200..205 ],
    cel = [
      {
        id = "passwords_match",
        msg = "the passwords do not match",
        expr = "this.password == this.repeated_password"
      }
    ],

    include(reusable_fields),
    include_oneof(reusable_oneof),
    1 => uint64!("id", |id| id.gt(0)),
    2 => msg_field!(repeated user_msg, "best_friend"),
    3 => string!("password", |pw| pw.min_len(8)),
    4 => string!("repeated_password", |pw| pw.min_len(8)),
    5 => enum_field!(user_status_enum, "last_status", |status| status.defined_only()),
    6 => enum_map!("last_30_days_statuses", <int32, user_status_enum>, |m, k, v| m.min_pairs(30).keys(k.lt(31)).values(v.defined_only())),
    7 => msg_map!("friends", <uint64, user_msg>),
    8 => msg_field!(post_msg, "last_post"),
    9 => enum_field!(post_status_enum, "last_post_status"),
    10 => msg_field!(post_metadata_msg, "last_post_metadata"),


    enum "favorite_category" {
      include(reusable_variants),

      1 => "PETS",
      2 => "COOKING"
    }

    enum "tier" {
      options = [ example_option.clone() ],
      reserved_names = [ "one", "two" ],
      reserved = [ 405, 200..205 ],
      include(reusable_variants),

      1 => "SILVER",
      2 => "GOLD"
    }

    oneof "contact" {
      required,
      options = [ example_option.clone() ],

      11 => string!("email", |v| v.email()),
      12 => enum_field!(referrers_enum, "referrer"),
    }
  };

  message! {
    subsection_msg,

    options = [ example_option.clone() ],

    1 => string!("name"),
    2 => msg_field!(post_msg, "top_trending_post"),
  };

  message! {
    sub_subsection_msg,

    1 => uint64!("id"),
    2 => string!("name"),
    3 => enum_field!(post_category_enum, "category"),
  };

  let proto_root = Path::new("proto");
  package.render_templates(proto_root)?;
  Ok(())
}