diff --git a/remote_log/README.md b/remote_log/README.md index 19aa6ed..8c1061f 100644 --- a/remote_log/README.md +++ b/remote_log/README.md @@ -98,3 +98,7 @@ Actually use them, and check enum and dispatching etc. Put in Makefile nimble install https://github.com/PMunch/protobuf-nim + +### Protobuf service generation +Install `nimtwirp`, then: +`nimtwirp_build -I:. --out:. protocol.proto ` diff --git a/remote_log/hello-twirp/protocol_pb.nim b/remote_log/hello-twirp/protocol_pb.nim new file mode 100644 index 0000000..582a1a8 --- /dev/null +++ b/remote_log/hello-twirp/protocol_pb.nim @@ -0,0 +1,237 @@ +# Generated by protoc_gen_nim. Do not edit! + +import base64 +import intsets +import json +import strutils + +import nimpb/nimpb +import nimpb/json as nimpb_json + +type + vac_cas_CASRequest* = ref vac_cas_CASRequestObj + vac_cas_CASRequestObj* = object of Message + id: string + vac_cas_CASResponse* = ref vac_cas_CASResponseObj + vac_cas_CASResponseObj* = object of Message + id: string + data: string + +proc newvac_cas_CASResponse*(): vac_cas_CASResponse +proc newvac_cas_CASResponse*(data: string): vac_cas_CASResponse +proc newvac_cas_CASResponse*(data: seq[byte]): vac_cas_CASResponse +proc writevac_cas_CASResponse*(stream: Stream, message: vac_cas_CASResponse) +proc readvac_cas_CASResponse*(stream: Stream): vac_cas_CASResponse +proc sizeOfvac_cas_CASResponse*(message: vac_cas_CASResponse): uint64 +proc toJson*(message: vac_cas_CASResponse): JsonNode +proc parsevac_cas_CASResponse*(obj: JsonNode): vac_cas_CASResponse + +proc newvac_cas_CASRequest*(): vac_cas_CASRequest +proc newvac_cas_CASRequest*(data: string): vac_cas_CASRequest +proc newvac_cas_CASRequest*(data: seq[byte]): vac_cas_CASRequest +proc writevac_cas_CASRequest*(stream: Stream, message: vac_cas_CASRequest) +proc readvac_cas_CASRequest*(stream: Stream): vac_cas_CASRequest +proc sizeOfvac_cas_CASRequest*(message: vac_cas_CASRequest): uint64 +proc toJson*(message: vac_cas_CASRequest): JsonNode +proc parsevac_cas_CASRequest*(obj: JsonNode): vac_cas_CASRequest + +proc fullyQualifiedName*(T: typedesc[vac_cas_CASResponse]): string = "vac.cas.CASResponse" + +proc readvac_cas_CASResponseImpl(stream: Stream): Message = readvac_cas_CASResponse(stream) +proc writevac_cas_CASResponseImpl(stream: Stream, msg: Message) = writevac_cas_CASResponse(stream, vac_cas_CASResponse(msg)) +proc toJsonvac_cas_CASResponseImpl(msg: Message): JsonNode = toJson(vac_cas_CASResponse(msg)) +proc fromJsonvac_cas_CASResponseImpl(node: JsonNode): Message = parsevac_cas_CASResponse(node) + +proc vac_cas_CASResponseProcs*(): MessageProcs = + result.readImpl = readvac_cas_CASResponseImpl + result.writeImpl = writevac_cas_CASResponseImpl + result.toJsonImpl = toJsonvac_cas_CASResponseImpl + result.fromJsonImpl = fromJsonvac_cas_CASResponseImpl + +proc newvac_cas_CASResponse*(): vac_cas_CASResponse = + new(result) + initMessage(result[]) + result.procs = vac_cas_CASResponseProcs() + result.id = "" + result.data = "" + +proc clearid*(message: vac_cas_CASResponse) = + message.id = "" + +proc setid*(message: vac_cas_CASResponse, value: string) = + message.id = value + +proc id*(message: vac_cas_CASResponse): string {.inline.} = + message.id + +proc `id=`*(message: vac_cas_CASResponse, value: string) {.inline.} = + setid(message, value) + +proc cleardata*(message: vac_cas_CASResponse) = + message.data = "" + +proc setdata*(message: vac_cas_CASResponse, value: string) = + message.data = value + +proc data*(message: vac_cas_CASResponse): string {.inline.} = + message.data + +proc `data=`*(message: vac_cas_CASResponse, value: string) {.inline.} = + setdata(message, value) + +proc sizeOfvac_cas_CASResponse*(message: vac_cas_CASResponse): uint64 = + if len(message.id) > 0: + result = result + sizeOfTag(2, WireType.LengthDelimited) + result = result + sizeOfString(message.id) + if len(message.data) > 0: + result = result + sizeOfTag(3, WireType.LengthDelimited) + result = result + sizeOfString(message.data) + result = result + sizeOfUnknownFields(message) + +proc writevac_cas_CASResponse*(stream: Stream, message: vac_cas_CASResponse) = + if len(message.id) > 0: + protoWriteString(stream, message.id, 2) + if len(message.data) > 0: + protoWriteString(stream, message.data, 3) + writeUnknownFields(stream, message) + +proc readvac_cas_CASResponse*(stream: Stream): vac_cas_CASResponse = + result = newvac_cas_CASResponse() + while not atEnd(stream): + let + tag = readTag(stream) + wireType = wireType(tag) + case fieldNumber(tag) + of 0: + raise newException(InvalidFieldNumberError, "Invalid field number: 0") + of 2: + expectWireType(wireType, WireType.LengthDelimited) + setid(result, protoReadString(stream)) + of 3: + expectWireType(wireType, WireType.LengthDelimited) + setdata(result, protoReadString(stream)) + else: readUnknownField(stream, result, tag) + +proc toJson*(message: vac_cas_CASResponse): JsonNode = + result = newJObject() + if len(message.id) > 0: + result["id"] = %message.id + if len(message.data) > 0: + result["data"] = %message.data + +proc parsevac_cas_CASResponse*(obj: JsonNode): vac_cas_CASResponse = + result = newvac_cas_CASResponse() + var node: JsonNode + if obj.kind != JObject: + raise newException(nimpb_json.ParseError, "object expected") + node = getJsonField(obj, "id", "id") + if node != nil and node.kind != JNull: + setid(result, parseString(node)) + node = getJsonField(obj, "data", "data") + if node != nil and node.kind != JNull: + setdata(result, parseString(node)) + +proc serialize*(message: vac_cas_CASResponse): string = + let + ss = newStringStream() + writevac_cas_CASResponse(ss, message) + result = ss.data + +proc newvac_cas_CASResponse*(data: string): vac_cas_CASResponse = + let + ss = newStringStream(data) + result = readvac_cas_CASResponse(ss) + +proc newvac_cas_CASResponse*(data: seq[byte]): vac_cas_CASResponse = + let + ss = newStringStream(cast[string](data)) + result = readvac_cas_CASResponse(ss) + + +proc fullyQualifiedName*(T: typedesc[vac_cas_CASRequest]): string = "vac.cas.CASRequest" + +proc readvac_cas_CASRequestImpl(stream: Stream): Message = readvac_cas_CASRequest(stream) +proc writevac_cas_CASRequestImpl(stream: Stream, msg: Message) = writevac_cas_CASRequest(stream, vac_cas_CASRequest(msg)) +proc toJsonvac_cas_CASRequestImpl(msg: Message): JsonNode = toJson(vac_cas_CASRequest(msg)) +proc fromJsonvac_cas_CASRequestImpl(node: JsonNode): Message = parsevac_cas_CASRequest(node) + +proc vac_cas_CASRequestProcs*(): MessageProcs = + result.readImpl = readvac_cas_CASRequestImpl + result.writeImpl = writevac_cas_CASRequestImpl + result.toJsonImpl = toJsonvac_cas_CASRequestImpl + result.fromJsonImpl = fromJsonvac_cas_CASRequestImpl + +proc newvac_cas_CASRequest*(): vac_cas_CASRequest = + new(result) + initMessage(result[]) + result.procs = vac_cas_CASRequestProcs() + result.id = "" + +proc clearid*(message: vac_cas_CASRequest) = + message.id = "" + +proc setid*(message: vac_cas_CASRequest, value: string) = + message.id = value + +proc id*(message: vac_cas_CASRequest): string {.inline.} = + message.id + +proc `id=`*(message: vac_cas_CASRequest, value: string) {.inline.} = + setid(message, value) + +proc sizeOfvac_cas_CASRequest*(message: vac_cas_CASRequest): uint64 = + if len(message.id) > 0: + result = result + sizeOfTag(2, WireType.LengthDelimited) + result = result + sizeOfString(message.id) + result = result + sizeOfUnknownFields(message) + +proc writevac_cas_CASRequest*(stream: Stream, message: vac_cas_CASRequest) = + if len(message.id) > 0: + protoWriteString(stream, message.id, 2) + writeUnknownFields(stream, message) + +proc readvac_cas_CASRequest*(stream: Stream): vac_cas_CASRequest = + result = newvac_cas_CASRequest() + while not atEnd(stream): + let + tag = readTag(stream) + wireType = wireType(tag) + case fieldNumber(tag) + of 0: + raise newException(InvalidFieldNumberError, "Invalid field number: 0") + of 2: + expectWireType(wireType, WireType.LengthDelimited) + setid(result, protoReadString(stream)) + else: readUnknownField(stream, result, tag) + +proc toJson*(message: vac_cas_CASRequest): JsonNode = + result = newJObject() + if len(message.id) > 0: + result["id"] = %message.id + +proc parsevac_cas_CASRequest*(obj: JsonNode): vac_cas_CASRequest = + result = newvac_cas_CASRequest() + var node: JsonNode + if obj.kind != JObject: + raise newException(nimpb_json.ParseError, "object expected") + node = getJsonField(obj, "id", "id") + if node != nil and node.kind != JNull: + setid(result, parseString(node)) + +proc serialize*(message: vac_cas_CASRequest): string = + let + ss = newStringStream() + writevac_cas_CASRequest(ss, message) + result = ss.data + +proc newvac_cas_CASRequest*(data: string): vac_cas_CASRequest = + let + ss = newStringStream(data) + result = readvac_cas_CASRequest(ss) + +proc newvac_cas_CASRequest*(data: seq[byte]): vac_cas_CASRequest = + let + ss = newStringStream(cast[string](data)) + result = readvac_cas_CASRequest(ss) + + diff --git a/remote_log/hello-twirp/protocol_twirp.nim b/remote_log/hello-twirp/protocol_twirp.nim new file mode 100644 index 0000000..91a0194 --- /dev/null +++ b/remote_log/hello-twirp/protocol_twirp.nim @@ -0,0 +1,112 @@ +import asyncdispatch +import asynchttpserver +import httpclient +import json +import strutils + +import protocol_pb + +import nimtwirp/nimtwirp +import nimtwirp/errors + +const + CASPrefix* = "/twirp/vac.cas.CAS/" + +type + CAS* = ref CASObj + CASObj* = object of RootObj + GetImpl*: proc (service: CAS, param: vac_cas_CASRequest): Future[vac_cas_CASResponse] {.gcsafe, closure.} + PostImpl*: proc (service: CAS, param: vac_cas_CASRequest): Future[vac_cas_CASResponse] {.gcsafe, closure.} + +proc Get*(service: CAS, param: vac_cas_CASRequest): Future[vac_cas_CASResponse] {.async.} = + if service.GetImpl == nil: + raise newTwirpError(TwirpUnimplemented, "Get is not implemented") + result = await service.GetImpl(service, param) + +proc Post*(service: CAS, param: vac_cas_CASRequest): Future[vac_cas_CASResponse] {.async.} = + if service.PostImpl == nil: + raise newTwirpError(TwirpUnimplemented, "Post is not implemented") + result = await service.PostImpl(service, param) + +proc newCAS*(): CAS = + new(result) + +proc handleRequest*(service: CAS, req: Request): Future[nimtwirp.Response] {.async.} = + let (contentType, methodName) = validateRequest(req, CASPrefix) + + if methodName == "Get": + var inputMsg: vac_cas_CASRequest + + if contentType == "application/protobuf": + inputMsg = newvac_cas_CASRequest(req.body) + elif contentType == "application/json": + let node = parseJson(req.body) + inputMsg = parsevac_cas_CASRequest(node) + + let outputMsg = await Get(service, inputMsg) + + if contentType == "application/protobuf": + return nimtwirp.newResponse(serialize(outputMsg)) + elif contentType == "application/json": + return nimtwirp.newResponse(toJson(outputMsg)) + elif methodName == "Post": + var inputMsg: vac_cas_CASRequest + + if contentType == "application/protobuf": + inputMsg = newvac_cas_CASRequest(req.body) + elif contentType == "application/json": + let node = parseJson(req.body) + inputMsg = parsevac_cas_CASRequest(node) + + let outputMsg = await Post(service, inputMsg) + + if contentType == "application/protobuf": + return nimtwirp.newResponse(serialize(outputMsg)) + elif contentType == "application/json": + return nimtwirp.newResponse(toJson(outputMsg)) + else: + raise newTwirpError(TwirpBadRoute, "unknown method") + + +type + CASClient* = ref object of nimtwirp.Client + +proc newCASClient*(address: string, kind = ClientKind.Protobuf): CASClient = + new(result) + result.client = newHttpClient() + result.kind = kind + case kind + of ClientKind.Protobuf: + result.client.headers = newHttpHeaders({"Content-Type": "application/protobuf"}) + of ClientKind.Json: + result.client.headers = newHttpHeaders({"Content-Type": "application/json"}) + result.address = address + +proc Get*(client: CASClient, req: vac_cas_CASRequest): vac_cas_CASResponse = + var body: string + case client.kind + of ClientKind.Protobuf: + body = serialize(req) + of ClientKind.Json: + body = $toJson(req) + let resp = request(client, CASPrefix, "Get", body) + case client.kind + of ClientKind.Protobuf: + result = newvac_cas_CASResponse(resp.body) + of ClientKind.Json: + result = parsevac_cas_CASResponse(parseJson(resp.body)) + +proc Post*(client: CASClient, req: vac_cas_CASRequest): vac_cas_CASResponse = + var body: string + case client.kind + of ClientKind.Protobuf: + body = serialize(req) + of ClientKind.Json: + body = $toJson(req) + let resp = request(client, CASPrefix, "Post", body) + case client.kind + of ClientKind.Protobuf: + result = newvac_cas_CASResponse(resp.body) + of ClientKind.Json: + result = parsevac_cas_CASResponse(parseJson(resp.body)) +