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 : JSONType; 25 26 if (value.type == JSONType.null_) 27 { 28 paths = protoDefaultValue!(string[]); 29 return this; 30 } 31 32 enforce!ProtobufException(value.type == JSONType..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([]).toJSONValue == JSONValue("")); 43 assert(FieldMask(["foo"]).toJSONValue == JSONValue("foo")); 44 assert(FieldMask(["foo", "bar_baz"]).toJSONValue == JSONValue("foo,barBaz")); 45 assert(FieldMask(["foo", "bar_baz.qux"]).toJSONValue == JSONValue("foo,barBaz.qux")); 46 } 47 48 unittest 49 { 50 FieldMask foo; 51 assert(FieldMask([]) == foo.fromJSONValue(JSONValue(null))); 52 assert(FieldMask([]) == foo.fromJSONValue(JSONValue(""))); 53 assert(FieldMask(["foo"]) == foo.fromJSONValue(JSONValue("foo"))); 54 assert(FieldMask(["foo", "bar_baz"]) == foo.fromJSONValue(JSONValue("foo,barBaz"))); 55 assert(FieldMask(["foo", "bar_baz.qux"]) == foo.fromJSONValue(JSONValue("foo,barBaz.qux"))); 56 } 57 58 string toCamelCase(string snakeCase) pure 59 { 60 import std.array : Appender; 61 import std.ascii : isLower, isUpper, toUpper; 62 import std.exception : enforce; 63 64 Appender!string result; 65 bool capitalizeNext; 66 bool wordStart = true; 67 foreach (c; snakeCase) 68 { 69 enforce!ProtobufException(!c.isUpper, "Invalid field mask " ~ snakeCase); 70 enforce!ProtobufException(!wordStart || c.isLower || c == '_' || c == '.', "Invalid field mask " ~ snakeCase); 71 wordStart = (c == '_' || c == '.'); 72 if (c == '_') 73 { 74 enforce!ProtobufException(!capitalizeNext, "Invalid field mask " ~ snakeCase); 75 capitalizeNext = true; 76 continue; 77 } 78 if (capitalizeNext) 79 { 80 result ~= c.toUpper; 81 capitalizeNext = false; 82 continue; 83 } 84 result ~= c; 85 } 86 87 return result.data; 88 } 89 90 unittest 91 { 92 import std.exception : assertThrown; 93 94 assert("".toCamelCase == ""); 95 assert("foo".toCamelCase == "foo"); 96 assert("foo1".toCamelCase == "foo1"); 97 assert("foo_bar".toCamelCase == "fooBar"); 98 assert("_foo_bar".toCamelCase == "FooBar"); 99 assert("foo_bar_baz_qux".toCamelCase == "fooBarBazQux"); 100 assertThrown!ProtobufException("__".toCamelCase); 101 assertThrown!ProtobufException("foo__bar".toCamelCase); 102 assertThrown!ProtobufException("fooBar".toCamelCase); 103 assertThrown!ProtobufException("foo_1".toCamelCase); 104 assertThrown!ProtobufException("1_foo".toCamelCase); 105 assertThrown!ProtobufException("_1_foo".toCamelCase); 106 107 assert("foo.bar".toCamelCase == "foo.bar"); 108 assert(".foo..bar.".toCamelCase == ".foo..bar."); 109 assert("foo_bar.baz_qux".toCamelCase == "fooBar.bazQux"); 110 } 111 112 string toSnakeCase(string camelCase) pure 113 { 114 import std.array : Appender; 115 import std.ascii : isUpper, toLower; 116 import std.exception : enforce; 117 118 Appender!string result; 119 120 foreach (c; camelCase) 121 { 122 enforce!ProtobufException(c != '_', "Invalid field mask " ~ camelCase); 123 if (c.isUpper) 124 { 125 result ~= '_'; 126 result ~= c.toLower; 127 } 128 else 129 { 130 result ~= c; 131 } 132 } 133 134 return result.data; 135 } 136 137 unittest 138 { 139 import std.exception : assertThrown; 140 141 assert("".toSnakeCase == ""); 142 assert("fooBar".toSnakeCase == "foo_bar"); 143 assert("foo1".toSnakeCase == "foo1"); 144 assert("FooBar".toSnakeCase == "_foo_bar"); 145 assert("fooBarBazQux".toSnakeCase == "foo_bar_baz_qux"); 146 assertThrown!ProtobufException("foo_Bar".toSnakeCase); 147 assertThrown!ProtobufException("foo_Bar".toSnakeCase); 148 149 assert("foo.bar".toSnakeCase == "foo.bar"); 150 assert(".foo..bar.".toSnakeCase == ".foo..bar."); 151 assert("fooBar.bazQux".toSnakeCase == "foo_bar.baz_qux"); 152 }