-
-
Notifications
You must be signed in to change notification settings - Fork 8
/
d0d9f8e7.8a2fee4e.js
1 lines (1 loc) · 21.4 KB
/
d0d9f8e7.8a2fee4e.js
1
(window.webpackJsonp=window.webpackJsonp||[]).push([[42],{110:function(e,n,a){"use strict";a.r(n),a.d(n,"frontMatter",(function(){return s})),a.d(n,"metadata",(function(){return l})),a.d(n,"toc",(function(){return c})),a.d(n,"default",(function(){return p}));var t=a(3),i=a(7),r=(a(0),a(119)),o=["components"],s={title:"Generated Code"},l={unversionedId:"generated-code",id:"generated-code",isDocsHomePage:!1,title:"Generated Code",description:"This page describes the code generated by ScalaPB and how to use it.",source:"@site/../docs/target/mdoc/generated-code.md",slug:"/generated-code",permalink:"/docs/generated-code",version:"current",sidebar:"someSidebar",previous:{title:"Transformations",permalink:"/docs/transformations"},next:{title:"Sealed oneofs",permalink:"/docs/sealed-oneofs"}},c=[{value:"Default Package Structure",id:"default-package-structure",children:[]},{value:"Messages",id:"messages",children:[{value:"Building New Messages",id:"building-new-messages",children:[]},{value:"Updating Messages",id:"updating-messages",children:[]}]},{value:"Optional Fields",id:"optional-fields",children:[]},{value:"Required Fields",id:"required-fields",children:[]},{value:"Repeated Fields",id:"repeated-fields",children:[]},{value:"Oneof Fields",id:"oneof-fields",children:[]},{value:"Enumerations",id:"enumerations",children:[]},{value:"ASCII Representation",id:"ascii-representation",children:[]},{value:"Java Conversions",id:"java-conversions",children:[]}],d={toc:c};function p(e){var n=e.components,a=Object(i.a)(e,o);return Object(r.b)("wrapper",Object(t.a)({},d,a,{components:n,mdxType:"MDXLayout"}),Object(r.b)("p",null,"This page describes the code generated by ScalaPB and how to use it."),Object(r.b)("h2",{id:"default-package-structure"},"Default Package Structure"),Object(r.b)("p",null,"The generator will create a Scala file for each top-level message and enum\nin your proto file. Using multiple files results in a better incremental\ncompilation performance."),Object(r.b)("p",null,"The Scala package of the generated files will be determined as follows:"),Object(r.b)("ul",null,Object(r.b)("li",{parentName:"ul"},"If the ",Object(r.b)("inlineCode",{parentName:"li"},"java_package")," option is defined, then the Scala package will be\n",Object(r.b)("inlineCode",{parentName:"li"},"java_package.base_name")," where ",Object(r.b)("inlineCode",{parentName:"li"},"base_name")," is the name of the proto file\nwithout the ",Object(r.b)("inlineCode",{parentName:"li"},".proto")," extension."),Object(r.b)("li",{parentName:"ul"},"If the ",Object(r.b)("inlineCode",{parentName:"li"},"java_package")," is undefined, but the file specifies a package name\nvia the ",Object(r.b)("inlineCode",{parentName:"li"},"package")," keyword then the Scala package will be\n",Object(r.b)("inlineCode",{parentName:"li"},"package.base_name"),"."),Object(r.b)("li",{parentName:"ul"},"If neither ",Object(r.b)("inlineCode",{parentName:"li"},"java_package")," nor ",Object(r.b)("inlineCode",{parentName:"li"},"package")," are specified, the Scala package\nwill be just ",Object(r.b)("inlineCode",{parentName:"li"},"base_name"),".")),Object(r.b)("p",null,"From version 0.4.2, there is a way to customize the package name by using\n",Object(r.b)("a",{parentName:"p",href:"/docs/customizations"},"file-level options"),"."),Object(r.b)("h2",{id:"messages"},"Messages"),Object(r.b)("p",null,"Each message corresponds to a final case class and a companion object.\nOptional fields are wrapped in an ",Object(r.b)("inlineCode",{parentName:"p"},"Option[]"),", repeated fields are given as\n",Object(r.b)("inlineCode",{parentName:"p"},"Seq[]"),', and required fields (which you should not use, see "Required is\nforever" in the ',Object(r.b)("a",{parentName:"p",href:"https://developers.google.com/protocol-buffers/docs/proto#simple"},"Language\nGuide")," are\nnormal members."),Object(r.b)("p",null,"Note that in proto3, scalar (non-message) fields are not wrapped in ",Object(r.b)("inlineCode",{parentName:"p"},"Option"),","),Object(r.b)("p",null,"For example, if your protocol buffer looks like this:"),Object(r.b)("pre",null,Object(r.b)("code",{parentName:"pre",className:"language-protobuf"},"message Person {\n optional string name = 1;\n optional int32 age = 2;\n\n repeated Address addresses = 3;\n}\n\nmessage Address {\n optional string street = 1;\n optional string city = 2;\n}\n")),Object(r.b)("p",null,"...then the compiler will generate code that looks like this:"),Object(r.b)("pre",null,Object(r.b)("code",{parentName:"pre",className:"language-scala"},"final case class Person(\n name: Option[String] = None,\n age: Option[Int] = None,\n addresses: Seq[Address] = Nil, ...) extends GeneratedMessage {\n\n def toByteArray: Array[Byte] = { ... }\n\n // more stuff...\n}\n\nobject Person extends GeneratedMessageCompanion[Person] {\n def parseFrom(bytes: Array[Byte]): Person = { ... }\n\n // more stuff...\n}\n\n// similar stuff for Address...\n")),Object(r.b)("p",null,"The case class contains various methods for serialization, and the companion\nobject contains method for parsing. ",Object(r.b)("a",{parentName:"p",href:"https://github.com/scalapb/ScalaPB/blob/master/scalapb-runtime/src/main/scala/scalapb/GeneratedMessageCompanion.scala"},"See the source code for\n",Object(r.b)("inlineCode",{parentName:"a"},"GeneratedMessage")," and\n",Object(r.b)("inlineCode",{parentName:"a"},"GeneratedMessageCompanion")),"\nto see what methods are available"),Object(r.b)("h3",{id:"building-new-messages"},"Building New Messages"),Object(r.b)("p",null,"Create a new instance of a message by calling the constructor (as you normally\nwould for a case class):"),Object(r.b)("pre",null,Object(r.b)("code",{parentName:"pre",className:"language-scala"},'val p1 = Person()\n\nval p2 = Person(name = Some("John"))\n')),Object(r.b)("p",null,"When constructing messages, it is advised to use named arguments\nPerson(",Object(r.b)("strong",{parentName:"p"},"name")," = x, ",Object(r.b)("strong",{parentName:"p"},"age")," = y) to ensure your code does not rely on the\norder of the fields in the protocol buffer definition."),Object(r.b)("h3",{id:"updating-messages"},"Updating Messages"),Object(r.b)("p",null,"Messages are immutables: once you created a message instance it can not be\nchanged. Messages are thread-safe: you can access the same message instance\nfrom multiple threads."),Object(r.b)("p",null,"When you want to modify a message, simply create a new one based on the\noriginal. You can use the ",Object(r.b)("inlineCode",{parentName:"p"},"copy()")," method Scala provides for all case classes,\nhowever ScalaPB provides additional methods to make it even easier."),Object(r.b)("p",null,"The first method is using a ",Object(r.b)("inlineCode",{parentName:"p"},"withX()")," method, where ",Object(r.b)("inlineCode",{parentName:"p"},"X")," is a name of a field.\nFor example"),Object(r.b)("pre",null,Object(r.b)("code",{parentName:"pre",className:"language-scala"},'val p = Person().withName("John").withAge(29)\n')),Object(r.b)("p",null,"Note that when using the ",Object(r.b)("inlineCode",{parentName:"p"},"withX()")," method on an optional field, you do not\nneed to provide the ",Object(r.b)("inlineCode",{parentName:"p"},"Some()"),"."),Object(r.b)("p",null,"Another way to update a message is using the ",Object(r.b)("inlineCode",{parentName:"p"},"update()")," method:"),Object(r.b)("pre",null,Object(r.b)("code",{parentName:"pre",className:"language-scala"},'val p = Person().update(\n _.name := "John",\n _.age := 29\n)\n')),Object(r.b)("p",null,"The ",Object(r.b)("inlineCode",{parentName:"p"},"update()"),' method takes "mutations" (like the assignments above), and\napplies them on the object. Using the ',Object(r.b)("inlineCode",{parentName:"p"},"update()")," method, as we will see\nbelow, usually results in a more concise code, especially when your fields are\nmessage types."),Object(r.b)("h2",{id:"optional-fields"},"Optional Fields"),Object(r.b)("p",null,"Optional fields are wrapped inside an ",Object(r.b)("inlineCode",{parentName:"p"},"Option"),". The compiler will generate a\n",Object(r.b)("inlineCode",{parentName:"p"},"getX()")," method that will return the option's value if it is set, or a\ndefault value for the field if it is unset (that is, if it is ",Object(r.b)("inlineCode",{parentName:"p"},"None"),")"),Object(r.b)("p",null,"There are two ways to update an optional field using the ",Object(r.b)("inlineCode",{parentName:"p"},"update()")," method:"),Object(r.b)("pre",null,Object(r.b)("code",{parentName:"pre",className:"language-scala"},'val p = Person().update(\n // Pass the value directly:\n _.name := "John",\n\n // Use the optionalX prefix and pass an Option:\n _.optionalAge := Some(37) // ...or None\n)\n')),Object(r.b)("p",null,"The first way sets the field with a value, the second way lets you pass an\n",Object(r.b)("inlineCode",{parentName:"p"},"Option[]")," so it is possible to set the field to ",Object(r.b)("inlineCode",{parentName:"p"},"None"),"."),Object(r.b)("p",null,"For each optional field ",Object(r.b)("inlineCode",{parentName:"p"},"X"),", the compiler also generates a ",Object(r.b)("inlineCode",{parentName:"p"},"clearX()")," method\nthat returns a new instance of the message which is identical to the original\none except that the field is assigned the value ",Object(r.b)("inlineCode",{parentName:"p"},"None"),"."),Object(r.b)("h2",{id:"required-fields"},"Required Fields"),Object(r.b)("p",null,"Required fields have no default value in the generated constructor, so you\nmust specify them when you instantiate a new message. Differences from\noptional fields:"),Object(r.b)("ul",null,Object(r.b)("li",{parentName:"ul"},"The value of the field is not wrapped inside an ",Object(r.b)("inlineCode",{parentName:"li"},"Option"),"."),Object(r.b)("li",{parentName:"ul"},"There is no ",Object(r.b)("inlineCode",{parentName:"li"},"getX")," method (you can always use ",Object(r.b)("inlineCode",{parentName:"li"},".x"),")"),Object(r.b)("li",{parentName:"ul"},"There is no ",Object(r.b)("inlineCode",{parentName:"li"},"clearX")," method (since that will result in an invalid message)"),Object(r.b)("li",{parentName:"ul"},"There is no ",Object(r.b)("inlineCode",{parentName:"li"},"_.optionalX")," lens for the ",Object(r.b)("inlineCode",{parentName:"li"},"update()")," method.")),Object(r.b)("h2",{id:"repeated-fields"},"Repeated Fields"),Object(r.b)("p",null,"Repeated fields are provided as a ",Object(r.b)("inlineCode",{parentName:"p"},"Seq[T]"),". The compiler will generate the\nfollowing methods:"),Object(r.b)("ul",null,Object(r.b)("li",{parentName:"ul"},Object(r.b)("inlineCode",{parentName:"li"},"addFoo(f1, [f2, f3, ...]: Foo)"),": returns a copy with the given elements added to the\noriginal list."),Object(r.b)("li",{parentName:"ul"},Object(r.b)("inlineCode",{parentName:"li"},"addAllFoo(fs: Seq[Foo])"),": returns a copy with the given sequence of\nelements added to the original list."),Object(r.b)("li",{parentName:"ul"},Object(r.b)("inlineCode",{parentName:"li"},"withX(otherList)"),": replace the sequence with another."),Object(r.b)("li",{parentName:"ul"},Object(r.b)("inlineCode",{parentName:"li"},"clearX"),": replace with the sequence with an empty one.")),Object(r.b)("p",null,"Using ",Object(r.b)("inlineCode",{parentName:"p"},"update()")," is especially fun with repeated fields:"),Object(r.b)("pre",null,Object(r.b)("code",{parentName:"pre",className:"language-scala"},'val p = Person().update(\n // Override the addresses\n _.addresses := newListOfAddress,\n\n // Add one address:\n _.addresses :+= address1,\n\n // Add a list of addresses:\n _.addresses :++= Seq(address1, address2),\n\n // Modify an address in the list by index!\n _.addresses(1).street := "Townsend St.",\n\n // Modify all addresses:\n _.addresses.foreach(_.city := "San Francisco"),\n\n // Apply a transformation to all addresses (this is\n // just for showing off, it is not specific for\n // repeatables - it happens in the nested mutations)\n _.addresses.foreach(_.city.modify(_.trim))\n)\n')),Object(r.b)("h2",{id:"oneof-fields"},"Oneof Fields"),Object(r.b)("p",null,"Oneofs are great when you model a message that has multiple disjoint cases. An\nexample use case would be:"),Object(r.b)("pre",null,Object(r.b)("code",{parentName:"pre",className:"language-protobuf"},"// Represent a payment by credit card\nmessage CreditCardPayment {\n optional string last4 = 1;\n optional int32 expiration_month = 2;\n optional int32 expiration_year = 3;\n}\n\n// Represent a payment by credit card\nmessage BankTransferPayment {\n optional string routing_number = 1;\n optional string account_number = 2;\n}\n\n// Represents an order placed by a customer:\nmessage Order {\n optional int32 amount = 1;\n optional string customer_id = 2;\n\n // How did we get paid? At most one option must be set.\n oneof payment_type {\n CreditCardPayment credit_card = 3;\n BankTransferPayment bank = 4;\n }\n}\n\n")),Object(r.b)("p",null,"The compiler will generate code that looks like this:"),Object(r.b)("pre",null,Object(r.b)("code",{parentName:"pre",className:"language-scala"},"final case class CreditCardPayment { ... }\nfinal case class BankTransfer { ... }\n\ncase class Order(..., paymentType: Payment.PaymentType) {\n // Set the payment type to a specific case:\n def withCreditCard(v: CreditCardPayment): Order\n def withBank(v: BankTransferPayment): Order\n\n // Sets the entire payment type to a new value:\n def withPaymentType(v: PaymentType): Order\n\n // Sets the PaymentType to Empty\n def clearPaymentType: Order\n}\n\nobject Order {\n sealed trait PaymentType {\n def isEmpty: Boolean\n def isDefined: Boolean\n def isCreditCard: Boolean\n def isBank: Boolean\n def creditCard: Option[CreditCardPayment]\n def bank: Option[BankTransferPayment]\n }\n\n case object Empty extends PaymentType\n\n case class CreditCard(v: CreditCardPayment) extends PaymentType\n\n case class Bank(v: CreditCardPayment) extends PaymentType\n}\n")),Object(r.b)("p",null,"This enables writing coding like this:"),Object(r.b)("pre",null,Object(r.b)("code",{parentName:"pre",className:"language-scala"},'val o1 = Order()\n .withCreditCard(CreditCardPayment(last4 = Some("4848")))\n\n// which is equivalent to:\nval o2 = Order().update(\n _.creditCard.last4 := "4848")\n\n// This changes the payment type to a bank, so the credit card data is\n// not reachable any more through o3.\nval o3 = o1.update(_.bank.routingNumber := "333")\n\nif (o3.paymentType.isBank) {\n // Do something useful.\n}\n\n// Pattern matching:\nimport Order.PaymentType\no3.paymentType match {\n case PaymentType.CreditCard(cc) => // handle cc\n case PaymentType.Bank(b) => // handle b\n case PaymentType.Empty => // handle exceptional case...\n}\n\n// The one of values are available as Option too:\nval maybeRoutingNumber: Option[String] = o3.paymentType.bank.map {\n b => b.routingNumber\n}\n')),Object(r.b)("h2",{id:"enumerations"},"Enumerations"),Object(r.b)("p",null,"Enumerations are implemented using sealed traits that extend ",Object(r.b)("inlineCode",{parentName:"p"},"GeneratedEnum"),".\nThis approach, rather than using Scala's standard Enumeration type, allows\ngetting a warning from the Scala compiler when a pattern match is incomplete."),Object(r.b)("p",null,"For a definition like:"),Object(r.b)("pre",null,Object(r.b)("code",{parentName:"pre",className:"language-protobuf"},"enum Weather {\n SUNNY = 1;\n PARTLY_CLOUDY = 2;\n RAIN = 3;\n}\n\nmessage Forecast {\n optional Weather weather = 1;\n}\n")),Object(r.b)("p",null,"The compiler will generate:"),Object(r.b)("pre",null,Object(r.b)("code",{parentName:"pre",className:"language-scala"},'sealed trait Weather extends GeneratedEnum {\n def isSunny: Boolean\n def isPartlyCloudy: Boolean\n def isRain: Boolean\n}\n\nobject Weather extends GeneratedEnumCompanion[Weather] {\n case object SUNNY extends Weather {\n val value = 1\n val name = "SUNNY"\n }\n\n // Similarly for the other enum values...\n case object PARTLY_CLOUDY extends Weather { ... }\n case object RAIN extends Weather { ... }\n\n // In ScalaPB >= 0.5.x, this captures unknown value that are received\n // from the wire format. Earlier versions throw a MatchError when\n // this happens.\n case class Unrecognized(value: Int) extends Weather { ... }\n\n // And a list of all possible values:\n lazy val values = Seq(SUNNY, PARTLY_CLOUDY, RAIN)\n}\n\ncase class Forecast(weather: Option[Weather]) { ... }\n')),Object(r.b)("p",null,"And we can write:"),Object(r.b)("pre",null,Object(r.b)("code",{parentName:"pre",className:"language-scala"},"val f = Forecast().update(_.weather := Weather.PARTLY_CLOUDY)\n\nassert(f.weather == Some(Weather.PARTLY_CLOUDY)\n\nif (f.getWeather.isRain) {\n // take an umbrella\n}\n\n// Pattern matching:\nf.getWeather match {\n case Weather.RAIN =>\n case Weather.SUNNY =>\n case _ =>\n}\n\n")),Object(r.b)("h2",{id:"ascii-representation"},"ASCII Representation"),Object(r.b)("p",null,"Each message case-class has ",Object(r.b)("inlineCode",{parentName:"p"},"toProtoString")," method that returns a string\nrepresentation of the message in an ASCII format. The ASCII format can be\nparsed back by the ",Object(r.b)("inlineCode",{parentName:"p"},"fromAscii()")," method available on the companion object."),Object(r.b)("p",null,"That format is not officially documented, but at least the standard Python,\nJava and C++ implementations of protobuf attempt to generate (and be able to parse)\ncompatible ASCII representations. ScalaPB's ",Object(r.b)("inlineCode",{parentName:"p"},"toString()")," and ",Object(r.b)("inlineCode",{parentName:"p"},"fromAscii"),"\nfollow the Java implementation."),Object(r.b)("p",null,"The format looks like this:"),Object(r.b)("pre",null,Object(r.b)("code",{parentName:"pre",className:"language-protobuf"},'int_field: 17\nstring_field: "foo"\nrepeated_string_field: "foo"\nrepeated_string_field: "bar"\nmessage_field {\n field1: "value1"\n color_enum: BLUE\n}\n')),Object(r.b)("p",null,"This format can be useful for debugging or for transient data processing, but\nbeware of persisting these ASCII representations: unknown fields throw an\nexception, and unlike the binary format, the ASCII format is senstitive to\nrenames."),Object(r.b)("h2",{id:"java-conversions"},"Java Conversions"),Object(r.b)("p",null,"If you are dealing with legacy Java protocol buffer code, while still wanting\nto write new code using ScalaPB, it can be useful to generate converters\nto/from the Java protocol buffers. To do this, set ",Object(r.b)("inlineCode",{parentName:"p"},"Compile / PB.targets"),"\nlike this in your ",Object(r.b)("inlineCode",{parentName:"p"},"build.sbt"),":"),Object(r.b)("pre",null,Object(r.b)("code",{parentName:"pre",className:"language-scala"},"Compile / PB.targets := Seq(\n PB.gens.java -> (Compile / sourceManaged).value,\n scalapb.gen(javaConversions=true) -> (Compile / sourceManaged).value\n)\n")),Object(r.b)("p",null,"This will result in the following changes:"),Object(r.b)("ul",null,Object(r.b)("li",{parentName:"ul"},"The companion object for each message will have ",Object(r.b)("inlineCode",{parentName:"li"},"fromJavaProto")," and\n",Object(r.b)("inlineCode",{parentName:"li"},"toJavaProto")," methods."),Object(r.b)("li",{parentName:"ul"},"The companion object for enums will have ",Object(r.b)("inlineCode",{parentName:"li"},"fromJavaValue")," and\n",Object(r.b)("inlineCode",{parentName:"li"},"toJavaValue")," methods.")))}p.isMDXComponent=!0},119:function(e,n,a){"use strict";a.d(n,"a",(function(){return p})),a.d(n,"b",(function(){return u}));var t=a(0),i=a.n(t);function r(e,n,a){return n in e?Object.defineProperty(e,n,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[n]=a,e}function o(e,n){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var t=Object.getOwnPropertySymbols(e);n&&(t=t.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),a.push.apply(a,t)}return a}function s(e){for(var n=1;n<arguments.length;n++){var a=null!=arguments[n]?arguments[n]:{};n%2?o(Object(a),!0).forEach((function(n){r(e,n,a[n])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(a)):o(Object(a)).forEach((function(n){Object.defineProperty(e,n,Object.getOwnPropertyDescriptor(a,n))}))}return e}function l(e,n){if(null==e)return{};var a,t,i=function(e,n){if(null==e)return{};var a,t,i={},r=Object.keys(e);for(t=0;t<r.length;t++)a=r[t],n.indexOf(a)>=0||(i[a]=e[a]);return i}(e,n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(t=0;t<r.length;t++)a=r[t],n.indexOf(a)>=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(i[a]=e[a])}return i}var c=i.a.createContext({}),d=function(e){var n=i.a.useContext(c),a=n;return e&&(a="function"==typeof e?e(n):s(s({},n),e)),a},p=function(e){var n=d(e.components);return i.a.createElement(c.Provider,{value:n},e.children)},b={inlineCode:"code",wrapper:function(e){var n=e.children;return i.a.createElement(i.a.Fragment,{},n)}},m=i.a.forwardRef((function(e,n){var a=e.components,t=e.mdxType,r=e.originalType,o=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),p=d(a),m=t,u=p["".concat(o,".").concat(m)]||p[m]||b[m]||r;return a?i.a.createElement(u,s(s({ref:n},c),{},{components:a})):i.a.createElement(u,s({ref:n},c))}));function u(e,n){var a=arguments,t=n&&n.mdxType;if("string"==typeof e||t){var r=a.length,o=new Array(r);o[0]=m;var s={};for(var l in n)hasOwnProperty.call(n,l)&&(s[l]=n[l]);s.originalType=e,s.mdxType="string"==typeof e?e:t,o[1]=s;for(var c=2;c<r;c++)o[c]=a[c];return i.a.createElement.apply(null,o)}return i.a.createElement.apply(null,a)}m.displayName="MDXCreateElement"}}]);