diff options
| author | Oskari Timperi <oskari.timperi@iki.fi> | 2018-04-09 22:46:27 +0300 |
|---|---|---|
| committer | Oskari Timperi <oskari.timperi@iki.fi> | 2018-04-09 22:46:27 +0300 |
| commit | 6c1197333e41763050d60f3939461bed37c348fd (patch) | |
| tree | 4c15bad2e2ede818ad6b43671c7274be462e5e65 /src | |
| parent | c8c8ff7fc0138188f649d18bde42e3a58655ef9f (diff) | |
| download | nimpb-6c1197333e41763050d60f3939461bed37c348fd.tar.gz nimpb-6c1197333e41763050d60f3939461bed37c348fd.zip | |
Fixes to JSON serialization + parsing
Diffstat (limited to 'src')
| -rw-r--r-- | src/nimpb/json.nim | 180 | ||||
| -rw-r--r-- | src/nimpb/util.nim | 55 |
2 files changed, 212 insertions, 23 deletions
diff --git a/src/nimpb/json.nim b/src/nimpb/json.nim index 6e99e11..0799364 100644 --- a/src/nimpb/json.nim +++ b/src/nimpb/json.nim @@ -1,10 +1,12 @@ import base64 import math +import pegs import std/json import strutils import times import nimpb +import util import wkt/duration_pb import wkt/timestamp_pb @@ -13,6 +15,9 @@ import wkt/field_mask_pb import wkt/struct_pb import wkt/any_pb +type + JsonParseError = object of nimpb.ParseError + proc `%`*(u: uint32): JsonNode = newJFloat(float(u)) @@ -110,7 +115,7 @@ proc toJson*(message: google_protobuf_Duration): JsonNode = if message.nanos != 0: add(s, ".") - add(s, intToStr(message.nanos, 9)) + add(s, intToStr(sgn(message.nanos) * message.nanos, 9)) removeSuffix(s, '0') add(s, "s") @@ -132,7 +137,15 @@ proc toJson*(message: google_protobuf_Timestamp): JsonNode = result = %s proc toJson*(message: google_protobuf_FieldMask): JsonNode = - %join(message.paths, ",") + var s = "" + + for path in message.paths: + add(s, toCamelCase(path)) + add(s, ",") + + setLen(s, len(s) - 1) + + result = %s proc toJson*(message: google_protobuf_Value): JsonNode @@ -241,7 +254,7 @@ proc parseInt*[T: int32|int64](node: JsonNode): T = elif node.kind == JInt: result = T(getBiggestInt(node)) else: - raise newException(ValueError, "not an integer") + raise newException(JsonParseError, "not an integer") proc parseInt*[T: uint32|uint64](node: JsonNode): T = if node.kind == JString: @@ -249,32 +262,32 @@ proc parseInt*[T: uint32|uint64](node: JsonNode): T = elif node.kind == JInt: result = T(getBiggestInt(node)) else: - raise newException(ValueError, "not an integer") + raise newException(JsonParseError, "not an integer") proc parseString*(node: JsonNode): string = if node.kind != JString: - raise newException(ValueError, "not a string") + raise newException(JsonParseError, "not a string") result = node.str proc parseBytes*(node: JsonNode): bytes = if node.kind != JString: - raise newException(ValueError, "not a string") + raise newException(JsonParseError, "not a string") result = bytes(base64.decode(node.str)) proc parseBool*(node: JsonNode): bool = if node.kind != JBool: - raise newException(ValueError, "not a boolean") + raise newException(JsonParseError, "not a boolean") result = getBool(node) proc parsegoogle_protobuf_DoubleValueFromJson*(node: JsonNode): google_protobuf_DoubleValue = if node.kind != JFloat: - raise newException(ValueError, "not a float") + raise newException(JsonParseError, "not a float") result = newgoogle_protobuf_DoubleValue() result.value = getFloat(node) proc parsegoogle_protobuf_FloatValueFromJson*(node: JsonNode): google_protobuf_FloatValue = if node.kind != JFloat: - raise newException(ValueError, "not a float") + raise newException(JsonParseError, "not a float") result = newgoogle_protobuf_FloatValue() result.value = getFloat(node) @@ -285,7 +298,7 @@ proc parsegoogle_protobuf_Int64ValueFromJson*(node: JsonNode): google_protobuf_I elif node.kind == JString: result.value = parseBiggestInt(node.str) else: - raise newException(ValueError, "invalid value") + raise newException(JsonParseError, "invalid value") proc parsegoogle_protobuf_UInt64ValueFromJson*(node: JsonNode): google_protobuf_UInt64Value = result = newgoogle_protobuf_UInt64Value() @@ -294,7 +307,7 @@ proc parsegoogle_protobuf_UInt64ValueFromJson*(node: JsonNode): google_protobuf_ elif node.kind == JString: result.value = uint64(parseBiggestUInt(node.str)) else: - raise newException(ValueError, "invalid value") + raise newException(JsonParseError, "invalid value") proc parsegoogle_protobuf_Int32ValueFromJson*(node: JsonNode): google_protobuf_Int32Value = result = newgoogle_protobuf_Int32Value() @@ -303,7 +316,7 @@ proc parsegoogle_protobuf_Int32ValueFromJson*(node: JsonNode): google_protobuf_I elif node.kind == JString: result.value = int32(parseInt(node.str)) else: - raise newException(ValueError, "invalid value") + raise newException(JsonParseError, "invalid value") proc parsegoogle_protobuf_UInt32ValueFromJson*(node: JsonNode): google_protobuf_UInt32Value = result = newgoogle_protobuf_UInt32Value() @@ -312,50 +325,171 @@ proc parsegoogle_protobuf_UInt32ValueFromJson*(node: JsonNode): google_protobuf_ elif node.kind == JString: result.value = uint32(parseUInt(node.str)) else: - raise newException(ValueError, "invalid value") + raise newException(JsonParseError, "invalid value") proc parsegoogle_protobuf_BoolValueFromJson*(node: JsonNode): google_protobuf_BoolValue = if node.kind != JBool: - raise newException(ValueError, "not a boolean") + raise newException(JsonParseError, "not a boolean") result = newgoogle_protobuf_BoolValue() result.value = getBool(node) proc parsegoogle_protobuf_StringValueFromJson*(node: JsonNode): google_protobuf_StringValue = if node.kind != JString: - raise newException(ValueError, "not a string") + raise newException(JsonParseError, "not a string") result = newgoogle_protobuf_StringValue() result.value = node.str proc parsegoogle_protobuf_BytesValueFromJson*(node: JsonNode): google_protobuf_BytesValue = if node.kind != JString: - raise newException(ValueError, "not a string") + raise newException(JsonParseError, "not a string") result = newgoogle_protobuf_BytesValue() result.value = bytes(base64.decode(node.str)) proc parsegoogle_protobuf_DurationFromJson*(node: JsonNode): google_protobuf_Duration = - discard + if node.kind != JString: + raise newException(JsonParseError, "string expected") + + if not endsWith(node.str, "s"): + raise newException(JsonParseError, "string does not end with s") + + result = newgoogle_protobuf_Duration() + + let dotIndex = find(node.str, '.') + + if dotIndex == -1: + result.seconds = parseBiggestInt(node.str[0..^2]) + if result.seconds < -315_576_000_000'i64 or + result.seconds > 315_576_000_000'i64: + raise newException(JsonParseError, "duration seconds out of bounds") + return + + result.seconds = parseBiggestInt(node.str[0..dotIndex-1]) + + if result.seconds < -315_576_000_000'i64 or + result.seconds > 315_576_000_000'i64: + raise newException(JsonParseError, "duration seconds out of bounds") + + let nanoStr = node.str[dotIndex+1..^2] + var factor = 100_000_000 + + result.nanos = 0 + + for ch in nanoStr: + result.nanos = result.nanos + int32(factor * (ord(ch) - ord('0'))) + factor = factor div 10 + + if result.nanos > 999_999_999: + raise newException(ValueError, "duration nanos out of bounds") + + if result.seconds < 0: + result.nanos = -result.nanos proc parsegoogle_protobuf_TimestampFromJson*(node: JsonNode): google_protobuf_Timestamp = - discard + if node.kind != JString: + raise newException(JsonParseError, "string expected") + + let pattern = peg""" +timestamp <- year '-' month '-' day 'T' hours ':' mins ':' secs {frac?} {offset} +year <- \d\d\d\d +month <- \d\d +day <- \d\d +hours <- \d\d +mins <- \d\d +secs <- \d\d +frac <- '.' \d+ +offset <- 'Z' / (('+' / '-') \d\d ':' \d\d) +""" + + var matches: array[2, string] + var s = node.str + var nanos = 0 + + if not match(s, pattern, matches): + raise newException(JsonParseError, "invalid timestamp") + + if len(matches[0]) > 0: + # Delete the fractions if they were found so that times.parse() can + # parse the rest of the timestamp. + let first = len(s) - len(matches[1]) - len(matches[0]) + let last = len(s) - len(matches[1]) - 1 + delete(s, first, last) + + var factor = 100_000_000 + for ch in matches[0][1..^1]: + nanos = nanos + factor * (ord(ch) - ord('0')) + factor = factor div 10 + + if nanos > 999_999_999: + raise newException(JsonParseError, "nanos out of bounds") + + var dt: DateTime + + try: + dt = times.parse(s, "yyyy-MM-dd'T'HH:mm:sszzz", utc()) + except Exception as exc: + raise newException(JsonParseError, exc.msg) + + if dt.year < 1 or dt.year > 9999: + raise newException(JsonParseError, "year out of bounds") + + result = newgoogle_protobuf_Timestamp() + + result.seconds = toUnix(toTime(dt)) + result.nanos = int32(nanos) proc parsegoogle_protobuf_FieldMaskFromJson*(node: JsonNode): google_protobuf_FieldMask = - discard + if node.kind != JString: + raise newException(JsonParseError, "string expected") + + result = newgoogle_protobuf_FieldMask() + result.paths = @[] + + for path in split(node.str, ","): + addPaths(result, toSnakeCase(path)) proc parsegoogle_protobuf_ValueFromJson*(node: JsonNode): google_protobuf_Value proc parsegoogle_protobuf_StructFromJson*(node: JsonNode): google_protobuf_Struct = - discard + if node.kind != JObject: + raise newException(JsonParseError, "object expected") + + result = newgoogle_protobuf_Struct() + + for key, valueNode in node: + let value = parsegoogle_protobuf_ValueFromJson(valueNode) + result.fields[key] = value proc parsegoogle_protobuf_ListValueFromJson*(node: JsonNode): google_protobuf_ListValue proc parsegoogle_protobuf_ValueFromJson*(node: JsonNode): google_protobuf_Value = - discard + result = newgoogle_protobuf_Value() + + if node.kind == JNull: + result.nullValue = google_protobuf_NullValue.NULL_VALUE + elif node.kind == JInt or node.kind == JFloat: + result.numberValue = getFloat(node) + elif node.kind == JString: + result.stringValue = getStr(node) + elif node.kind == JBool: + result.boolValue = getBool(node) + elif node.kind == JObject: + result.structValue = parsegoogle_protobuf_StructFromJson(node) + elif node.kind == JArray: + result.listValue = parsegoogle_protobuf_ListValueFromJson(node) proc parsegoogle_protobuf_NullValueFromJson*(node: JsonNode): google_protobuf_NullValue = - discard + if node.kind != JNull: + raise newException(JsonParseError, "null expected") + result = google_protobuf_NullValue.NULL_VALUE proc parsegoogle_protobuf_ListValueFromJson*(node: JsonNode): google_protobuf_ListValue = - discard + if node.kind != JArray: + raise newException(JsonParseError, "array expected") + + result = newgoogle_protobuf_ListValue() + + for value in node: + addValues(result, parsegoogle_protobuf_ValueFromJson(value)) proc parsegoogle_protobuf_AnyFromJson*(node: JsonNode): google_protobuf_Any = discard diff --git a/src/nimpb/util.nim b/src/nimpb/util.nim new file mode 100644 index 0000000..8220ef2 --- /dev/null +++ b/src/nimpb/util.nim @@ -0,0 +1,55 @@ +import strutils + +proc toCamelCase*(s: string): string = + ## Nimification of ToCamelCase() from + ## protobuf/src/google/protobuf/util/internal/utility.cc + result = newStringOfCap(len(s)) + + var + capitalizeNext = false + wasCapital = false + isCapital = false + firstWord = true + + for index in 0..len(s)-1: + let ch = s[index] + wasCapital = isCapital + isCapital = isUpperAscii(ch) + if ch == '_': + capitalizeNext = true + if len(result) > 0: + firstWord = false + elif firstWord: + if len(result) > 0 and isCapital and (not wasCapital or (index+1 < len(s) and isLowerAscii(s[index+1]))): + firstWord = false + add(result, ch) + else: + add(result, toLowerAscii(ch)) + elif capitalizeNext: + capitalizeNext = false + add(result, toUpperAscii(ch)) + else: + add(result, toLowerAscii(ch)) + +proc toSnakeCase*(s: string): string = + ## Nimification of ToSnakeCase() from + ## protobuf/src/google/protobuf/util/internal/utility.cc + result = newStringOfCap(len(s) * 2) + + var + wasNotUnderscore = false + wasNotCap = false + + for index in 0..len(s)-1: + if isUpperAscii(s[index]): + if wasNotUnderscore and + (wasNotCap or + (index+1 < len(s) and isLowerAscii(s[index+1]))): + add(result, '_') + add(result, toLowerAscii(s[index])) + wasNotUnderscore = true + wasNotCap = false + else: + add(result, s[index]) + wasNotUnderscore = s[index] != '_' + wasNotCap = true |
