1 module google.protobuf.field_mask; 2 3 import std.json : JSONValue; 4 import google.protobuf; 5 6 struct FieldMask 7 { 8 @Proto(1) string[] paths = protoDefaultValue!(string[]); 9 10 JSONValue toJSONValue()() 11 { 12 import std.algorithm : map; 13 import std.array : join; 14 import google.protobuf.json_encoding : toJSONValue; 15 16 return paths.map!(a => a.toCamelCase).join(",").toJSONValue; 17 } 18 19 FieldMask fromJSONValue()(JSONValue value) 20 { 21 import std.algorithm : map, splitter; 22 import std.array : array; 23 import std.exception : enforce; 24 import std.json : JSON_TYPE; 25 26 if (value.type == JSON_TYPE.NULL) 27 { 28 paths = protoDefaultValue!(string[]); 29 return this; 30 } 31 32 enforce!ProtobufException(value.type == JSON_TYPE.STRING, "FieldMask JSON encoding must be a string"); 33 34 paths = value.str.splitter(",").map!(a => a.toSnakeCase).array; 35 36 return this; 37 } 38 } 39 40 unittest 41 { 42 assert(FieldMask(["foo"]).toJSONValue == JSONValue("foo")); 43 assert(FieldMask(["foo", "bar_baz"]).toJSONValue == JSONValue("foo,barBaz")); 44 assert(FieldMask(["foo", "bar_baz.qux"]).toJSONValue == JSONValue("foo,barBaz.qux")); 45 } 46 47 unittest 48 { 49 FieldMask foo; 50 assert(FieldMask(["foo"]) == foo.fromJSONValue(JSONValue("foo"))); 51 assert(FieldMask(["foo", "bar_baz"]) == foo.fromJSONValue(JSONValue("foo,barBaz"))); 52 assert(FieldMask(["foo", "bar_baz.qux"]) == foo.fromJSONValue(JSONValue("foo,barBaz.qux"))); 53 } 54 55 string toCamelCase(string snakeCase) pure 56 { 57 import std.array : Appender; 58 import std.ascii : isLower, isUpper, toUpper; 59 import std.exception : enforce; 60 61 Appender!string result; 62 bool capitalizeNext; 63 bool wordStart = true; 64 foreach (c; snakeCase) 65 { 66 enforce!ProtobufException(!c.isUpper, "Invalid field mask " ~ snakeCase); 67 enforce!ProtobufException(!wordStart || c.isLower || c == '_' || c == '.', "Invalid field mask " ~ snakeCase); 68 wordStart = (c == '_' || c == '.'); 69 if (c == '_') 70 { 71 enforce!ProtobufException(!capitalizeNext, "Invalid field mask " ~ snakeCase); 72 capitalizeNext = true; 73 continue; 74 } 75 if (capitalizeNext) 76 { 77 result ~= c.toUpper; 78 capitalizeNext = false; 79 continue; 80 } 81 result ~= c; 82 } 83 84 return result.data; 85 } 86 87 unittest 88 { 89 import std.exception : assertThrown; 90 91 assert("foo".toCamelCase == "foo"); 92 assert("foo1".toCamelCase == "foo1"); 93 assert("foo_bar".toCamelCase == "fooBar"); 94 assert("_foo_bar".toCamelCase == "FooBar"); 95 assert("foo_bar_baz_qux".toCamelCase == "fooBarBazQux"); 96 assertThrown!ProtobufException("__".toCamelCase); 97 assertThrown!ProtobufException("foo__bar".toCamelCase); 98 assertThrown!ProtobufException("fooBar".toCamelCase); 99 assertThrown!ProtobufException("foo_1".toCamelCase); 100 assertThrown!ProtobufException("1_foo".toCamelCase); 101 assertThrown!ProtobufException("_1_foo".toCamelCase); 102 103 assert("foo.bar".toCamelCase == "foo.bar"); 104 assert(".foo..bar.".toCamelCase == ".foo..bar."); 105 assert("foo_bar.baz_qux".toCamelCase == "fooBar.bazQux"); 106 } 107 108 string toSnakeCase(string camelCase) pure 109 { 110 import std.array : Appender; 111 import std.ascii : isUpper, toLower; 112 import std.exception : enforce; 113 114 Appender!string result; 115 116 foreach (c; camelCase) 117 { 118 enforce!ProtobufException(c != '_', "Invalid field mask " ~ camelCase); 119 if (c.isUpper) 120 { 121 result ~= '_'; 122 result ~= c.toLower; 123 } 124 else 125 { 126 result ~= c; 127 } 128 } 129 130 return result.data; 131 } 132 133 unittest 134 { 135 import std.exception : assertThrown; 136 137 assert("".toSnakeCase == ""); 138 assert("fooBar".toSnakeCase == "foo_bar"); 139 assert("foo1".toSnakeCase == "foo1"); 140 assert("FooBar".toSnakeCase == "_foo_bar"); 141 assert("fooBarBazQux".toSnakeCase == "foo_bar_baz_qux"); 142 assertThrown!ProtobufException("foo_Bar".toSnakeCase); 143 assertThrown!ProtobufException("foo_Bar".toSnakeCase); 144 145 assert("foo.bar".toSnakeCase == "foo.bar"); 146 assert(".foo..bar.".toSnakeCase == ".foo..bar."); 147 assert("fooBar.bazQux".toSnakeCase == "foo_bar.baz_qux"); 148 }