aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOskari Timperi <oskari.timperi@iki.fi>2018-04-09 22:46:27 +0300
committerOskari Timperi <oskari.timperi@iki.fi>2018-04-09 22:46:27 +0300
commit6c1197333e41763050d60f3939461bed37c348fd (patch)
tree4c15bad2e2ede818ad6b43671c7274be462e5e65 /src
parentc8c8ff7fc0138188f649d18bde42e3a58655ef9f (diff)
downloadnimpb-6c1197333e41763050d60f3939461bed37c348fd.tar.gz
nimpb-6c1197333e41763050d60f3939461bed37c348fd.zip
Fixes to JSON serialization + parsing
Diffstat (limited to 'src')
-rw-r--r--src/nimpb/json.nim180
-rw-r--r--src/nimpb/util.nim55
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