-
-
Notifications
You must be signed in to change notification settings - Fork 8
/
e901dba7.83a400db.js
1 lines (1 loc) · 15.8 KB
/
e901dba7.83a400db.js
1
(window.webpackJsonp=window.webpackJsonp||[]).push([[46],{114:function(e,t,n){"use strict";n.r(t),n.d(t,"frontMatter",(function(){return p})),n.d(t,"metadata",(function(){return s})),n.d(t,"toc",(function(){return l})),n.d(t,"default",(function(){return b}));var a=n(3),o=n(7),i=(n(0),n(119)),r=["components"],p={title:"Transformations",layout:"docs"},s={unversionedId:"transformations",id:"transformations",isDocsHomePage:!1,title:"Transformations",description:"Introduction",source:"@site/../docs/target/mdoc/transformations.md",slug:"/transformations",permalink:"/docs/transformations",version:"current",sidebar:"someSidebar",previous:{title:"SBT Settings",permalink:"/docs/sbt-settings"},next:{title:"Generated Code",permalink:"/docs/generated-code"}},l=[{value:"Introduction",id:"introduction",children:[]},{value:"Example use-case",id:"example-use-case",children:[]},{value:"Syntax",id:"syntax",children:[{value:"Referencing rules values",id:"referencing-rules-values",children:[]}]},{value:"Example: customizing third-party types",id:"example-customizing-third-party-types",children:[]}],c={toc:l};function b(e){var t=e.components,n=Object(o.a)(e,r);return Object(i.b)("wrapper",Object(a.a)({},c,n,{components:t,mdxType:"MDXLayout"}),Object(i.b)("h2",{id:"introduction"},"Introduction"),Object(i.b)("p",null,"Field transformations were introduced in ScalaPB 0.10.10 and allow you to automatically apply ScalaPB field-level options when a given field match certain conditions. In the future, we expect to have transformations for additional protobuf entities."),Object(i.b)("p",null,"This document assumes that you are already familiar with:"),Object(i.b)("ul",null,Object(i.b)("li",{parentName:"ul"},Object(i.b)("a",{parentName:"li",href:"https://developers.google.com/protocol-buffers/docs/proto#customoptions"},"Protocol Buffer custom options"),"."),Object(i.b)("li",{parentName:"ul"},Object(i.b)("a",{parentName:"li",href:"/docs/customizations"},"ScalaPB customizations")," and type mappers."),Object(i.b)("li",{parentName:"ul"},"Protobuf Descriptors, and specifically ",Object(i.b)("a",{parentName:"li",href:"https://github.com/protocolbuffers/protobuf/blob/48234f5f012582843bb476ee3afef36cda94cb66/src/google/protobuf/descriptor.proto#L138-L239"},"FieldDescriptorProto"),".")),Object(i.b)("h2",{id:"example-use-case"},"Example use-case"),Object(i.b)("p",null,"Assume that your project uses a custom option called ",Object(i.b)("inlineCode",{parentName:"p"},"sensitive"),", and whenever this option is set to ",Object(i.b)("inlineCode",{parentName:"p"},"true")," on a ",Object(i.b)("inlineCode",{parentName:"p"},"string")," field, you would ScalaPB to use a custom type, ",Object(i.b)("inlineCode",{parentName:"p"},"SensitiveString")," instead of a standard ",Object(i.b)("inlineCode",{parentName:"p"},"String"),"."),Object(i.b)("p",null,"The custom option definition could look like this:"),Object(i.b)("pre",null,Object(i.b)("code",{parentName:"pre",className:"language-protobuf"},'// opts.proto\nsyntax = "proto2";\n\npackage mypkg;\n\nimport "google/protobuf/descriptor.proto";\n\nextend google.protobuf.FieldOptions {\n optional MyCustomOptions opts = 50001;\n}\n\nmessage MyCustomOptions {\n optional bool sensitive = 1;\n optional int32 num = 2;\n}\n')),Object(i.b)("p",null,"Example usage of the custom options:"),Object(i.b)("pre",null,Object(i.b)("code",{parentName:"pre",className:"language-protobuf"},'// usage.proto\n\nsyntax = "proto3";\n\npackage mypkg;\n\nimport "opts.proto";\n\nmessage User {\n string secret = 1 [(mypkg.opts).sensitive = true];\n}\n')),Object(i.b)("p",null,"We want the type of ",Object(i.b)("inlineCode",{parentName:"p"},"secret")," in the case class to be ",Object(i.b)("inlineCode",{parentName:"p"},"SensitiveString"),". We could manually set\n",Object(i.b)("inlineCode",{parentName:"p"},"(scalapb.field).type")," to ",Object(i.b)("inlineCode",{parentName:"p"},"SensitiveString"),":"),Object(i.b)("pre",null,Object(i.b)("code",{parentName:"pre",className:"language-protobuf"},'message User {\n string secret = 1 [(mypkg.opts).sensitive = true,\n (scalapb.field).type = "mypkg.SensitiveString"];\n}\n')),Object(i.b)("p",null,"but it would be nice if there was a way to automatically apply ",Object(i.b)("inlineCode",{parentName:"p"},"(scalapb.field).type")," automatically whenever ",Object(i.b)("inlineCode",{parentName:"p"},"sensitive")," was set to true on a ",Object(i.b)("inlineCode",{parentName:"p"},"string")," field. This is the problem field transformations are set to solve:"),Object(i.b)("pre",null,Object(i.b)("code",{parentName:"pre",className:"language-protobuf"},'// Usage.proto:\nsyntax = "proto3";\n\nimport "scalapb/scalapb.proto";\nimport "opts.proto";\n\noption (scalapb.options) = {\n field_transformations : [\n {\n when : {\n options {\n [mypkg.opts]{sensitive : true}\n }\n type: TYPE_STRING\n }\n set : {[scalapb.field] {type : \'mypkg.SensitiveString\'}}\n }\n}\n\nmessage User {\n string secret = 1 [(mypkg.opts).sensitive = true];\n}\n')),Object(i.b)("p",null,"The transformation above matches when the custom option ",Object(i.b)("inlineCode",{parentName:"p"},"senstive")," is true, and the field type\nis ",Object(i.b)("inlineCode",{parentName:"p"},"string"),". When it matches, it sets the ScalaPB option ",Object(i.b)("inlineCode",{parentName:"p"},"type"),"."),Object(i.b)("h2",{id:"syntax"},"Syntax"),Object(i.b)("p",null,"FieldTransformations are defined in ",Object(i.b)("a",{parentName:"p",href:"https://github.com/scalapb/ScalaPB/blob/master/protobuf/scalapb/scalapb.proto"},"scalapb.proto"),":"),Object(i.b)("pre",null,Object(i.b)("code",{parentName:"pre",className:"language-protobuf"},"enum MatchType {\n CONTAINS = 0;\n EXACT = 1;\n PRESENCE = 2;\n}\n\nmessage FieldTransformation {\n optional google.protobuf.FieldDescriptorProto when = 1;\n optional MatchType match_type = 2 [default=CONTAINS];\n optional google.protobuf.FieldOptions set = 3;\n}\n")),Object(i.b)("p",null,"ScalaPB has a file-level option ",Object(i.b)("inlineCode",{parentName:"p"},"field_transformations")," which is a ",Object(i.b)("inlineCode",{parentName:"p"},"repeated FieldTransformation"),". The scope\nof the field transformations is the same proto-file, and can be passed down to the entire package as a package-scoped\noption (when ",Object(i.b)("inlineCode",{parentName:"p"},"scope: PACKAGE")," option is set)."),Object(i.b)("p",null,"A field transformation matches on the ",Object(i.b)("inlineCode",{parentName:"p"},"when")," condition which a ",Object(i.b)("a",{parentName:"p",href:"https://github.com/protocolbuffers/protobuf/blob/48234f5f012582843bb476ee3afef36cda94cb66/src/google/protobuf/descriptor.proto#L138-L239"},"FieldDescriptorProto"),". This allows it to match on the field's type, or label (",Object(i.b)("inlineCode",{parentName:"p"},"LABEL_REPEATED"),", ",Object(i.b)("inlineCode",{parentName:"p"},"LABEL_OPTIONAL"),", ",Object(i.b)("inlineCode",{parentName:"p"},"LABEL_REQUIRED"),"), as well as on custom options like in the previous example. There are few matching modes that are described below and can be selected using ",Object(i.b)("inlineCode",{parentName:"p"},"match_type"),". The ",Object(i.b)("inlineCode",{parentName:"p"},"set")," field tells ScalaPB what options to apply to the field if the rule conditions match. Currently, only ",Object(i.b)("inlineCode",{parentName:"p"},"[scalapb.field]")," options may appear in the ",Object(i.b)("inlineCode",{parentName:"p"},"set")," field."),Object(i.b)("p",null,"There are three matching modes available:"),Object(i.b)("ul",null,Object(i.b)("li",{parentName:"ul"},Object(i.b)("inlineCode",{parentName:"li"},"CONTAINS")," is the default matching mode. In this mode, ScalaPB checks that all the options in the ",Object(i.b)("inlineCode",{parentName:"li"},"when")," pattern are defined on the field descriptor and having the same value (even if the field is repeated). Additional fields may be defined on the field besides the ones on the ",Object(i.b)("inlineCode",{parentName:"li"},"when")," pattern."),Object(i.b)("li",{parentName:"ul"},Object(i.b)("inlineCode",{parentName:"li"},"EXACT")," is a strict equality comparison between the ",Object(i.b)("inlineCode",{parentName:"li"},"when")," pattern and the field descriptor."),Object(i.b)("li",{parentName:"ul"},Object(i.b)("inlineCode",{parentName:"li"},"PRESENCE")," checks whether every field that is present on the ",Object(i.b)("inlineCode",{parentName:"li"},"when")," pattern is also present on the field's rules. The specific value the option has is not compared. This allows matching on any value. For example, ",Object(i.b)("inlineCode",{parentName:"li"},"{int32: {gt: 1}}")," would match for any number assigned to ",Object(i.b)("inlineCode",{parentName:"li"},"int32.gt"),". For repeated fields, ",Object(i.b)("inlineCode",{parentName:"li"},"PRESENCE")," verifies that the value is not empty. ")),Object(i.b)("h3",{id:"referencing-rules-values"},"Referencing rules values"),Object(i.b)("p",null,"It is possible to reference values in the rules and use them on the ",Object(i.b)("inlineCode",{parentName:"p"},"set")," part. Whenever there is a singular string field under the field descriptor, ScalaPB would replace tokens in the format ",Object(i.b)("inlineCode",{parentName:"p"},"$(p)")," with the value of the field's option at the path ",Object(i.b)("inlineCode",{parentName:"p"},"p"),", relative to the ",Object(i.b)("inlineCode",{parentName:"p"},"FieldDescriptorProto")," of the field. To reference extension fields, wrap the extension full name in brackets (",Object(i.b)("inlineCode",{parentName:"p"},"[]"),"). For example, ",Object(i.b)("inlineCode",{parentName:"p"},"$(options.[pkg.opts].num)")," would be substituted with the value of that option on the field. If the option is not set on the field, a default value will be replaced (0 for numeric types, empty string, and so on)."),Object(i.b)("p",null,"The paths that are referenced don't have to appear on the ",Object(i.b)("inlineCode",{parentName:"p"},"when")," pattern. While referencing rule values is useful when the matching mode is ",Object(i.b)("inlineCode",{parentName:"p"},"PRESENCE"),", it is supported to reference rule values in all matching modes."),Object(i.b)("p",null,"One application for this is in conjunction with ",Object(i.b)("a",{parentName:"p",href:"https://github.com/fthomas/refined"},"refined types"),". See example in ",Object(i.b)("a",{parentName:"p",href:"/docs/validation#using-with-refined"},"ScalaPB validate documentation"),"."),Object(i.b)("h2",{id:"example-customizing-third-party-types"},"Example: customizing third-party types"),Object(i.b)("p",null,"When you want to customize your own messages, ScalaPB lets you add ",Object(i.b)("a",{parentName:"p",href:"/docs/customizations#message-level-custom-type-and-boxing"},"custom\noptions")," within the message is defined. You may also want to apply customizations to types defined in third-party protos which you can not change. To accomplish that, we can use field transformations. In the following example, we match on ",Object(i.b)("inlineCode",{parentName:"p"},"google.protobuf.Timestamp")," and map it to a custom type. In ",Object(i.b)("inlineCode",{parentName:"p"},"src/main/protobuf/myexample/options.proto"),":"),Object(i.b)("pre",null,Object(i.b)("code",{parentName:"pre",className:"language-protobuf"},'syntax = "proto2";\n\npackage com.e;\n\nimport "scalapb/scalapb.proto";\n\noption (scalapb.options) = {\n scope: PACKAGE\n field_transformations : [\n {\n when : {\n type: TYPE_MESSAGE\n type_name: ".google.protobuf.Timestamp"\n }\n set : {[scalapb.field] {type : \'com.myexample.MyType\' }}\n }\n ]\n};\n')),Object(i.b)("div",{className:"admonition admonition-note alert alert--secondary"},Object(i.b)("div",{parentName:"div",className:"admonition-heading"},Object(i.b)("h5",{parentName:"div"},Object(i.b)("span",{parentName:"h5",className:"admonition-icon"},Object(i.b)("svg",{parentName:"span",xmlns:"http://www.w3.org/2000/svg",width:"14",height:"16",viewBox:"0 0 14 16"},Object(i.b)("path",{parentName:"svg",fillRule:"evenodd",d:"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"}))),"note")),Object(i.b)("div",{parentName:"div",className:"admonition-content"},Object(i.b)("p",{parentName:"div"},"Note the ",Object(i.b)("inlineCode",{parentName:"p"},".")," (dot) prefix in the ",Object(i.b)("inlineCode",{parentName:"p"},"type_name")," field above. It is needed as ",Object(i.b)("a",{parentName:"p",href:"https://github.com/protocolbuffers/protobuf/blob/68cb69ea68822d96eee6d6104463edf85e70d689/src/google/protobuf/descriptor.proto#L187-L192"},"explained here"),". In this example we assume the user's package is not named ",Object(i.b)("inlineCode",{parentName:"p"},"google")," or ",Object(i.b)("inlineCode",{parentName:"p"},"google.protobuf")," since then ",Object(i.b)("inlineCode",{parentName:"p"},"type_name")," could be relative and would not match."))),Object(i.b)("p",null,"Now, we need to make sure there is an implicit typemapper converting between ",Object(i.b)("inlineCode",{parentName:"p"},"google.protobuf.timestamp.Timestamp")," and ",Object(i.b)("inlineCode",{parentName:"p"},"com.myexample.MyType"),". The typemapper can be defined in the companion object of ",Object(i.b)("inlineCode",{parentName:"p"},"MyType")," as exampled in ",Object(i.b)("a",{parentName:"p",href:"/docs/customizations#custom-types"},"custom types"),"."))}b.isMDXComponent=!0},119:function(e,t,n){"use strict";n.d(t,"a",(function(){return b})),n.d(t,"b",(function(){return u}));var a=n(0),o=n.n(a);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function p(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?r(Object(n),!0).forEach((function(t){i(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):r(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function s(e,t){if(null==e)return{};var n,a,o=function(e,t){if(null==e)return{};var n,a,o={},i=Object.keys(e);for(a=0;a<i.length;a++)n=i[a],t.indexOf(n)>=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a<i.length;a++)n=i[a],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var l=o.a.createContext({}),c=function(e){var t=o.a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):p(p({},t),e)),n},b=function(e){var t=c(e.components);return o.a.createElement(l.Provider,{value:t},e.children)},d={inlineCode:"code",wrapper:function(e){var t=e.children;return o.a.createElement(o.a.Fragment,{},t)}},m=o.a.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,r=e.parentName,l=s(e,["components","mdxType","originalType","parentName"]),b=c(n),m=a,u=b["".concat(r,".").concat(m)]||b[m]||d[m]||i;return n?o.a.createElement(u,p(p({ref:t},l),{},{components:n})):o.a.createElement(u,p({ref:t},l))}));function u(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,r=new Array(i);r[0]=m;var p={};for(var s in t)hasOwnProperty.call(t,s)&&(p[s]=t[s]);p.originalType=e,p.mdxType="string"==typeof e?e:a,r[1]=p;for(var l=2;l<i;l++)r[l]=n[l];return o.a.createElement.apply(null,r)}return o.a.createElement.apply(null,n)}m.displayName="MDXCreateElement"}}]);