Skip to content

Commit

Permalink
Conversion of some protobuf types to lua types (#1)
Browse files Browse the repository at this point in the history
- add support for strings
- add support for numbers
- add support for bools
- add support for message
- add support for google.protobuf.timestamp
  • Loading branch information
KinNeko-De committed Jun 11, 2023
1 parent 0de4061 commit dbd7692
Show file tree
Hide file tree
Showing 38 changed files with 3,596 additions and 0 deletions.
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
on:
push:
paths-ignore:
- '**/**.md'

concurrency: ci-${{ github.ref }}

name: protobuf-go-ci

jobs:
ci:
name: "CI"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install go
uses: actions/setup-go@v3
with:
go-version: '^1.20.0'
- name: "Installed go version"
run: go version
- name: "Build"
run: |
go build ./...
- name: "Test"
run: |
go test ./... -race -coverprofile=coverage.out
- name: "Code coverage"
uses: codecov/codecov-action@v3
57 changes: 57 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
on:
workflow_dispatch

env:
MAJOR_MINOR_PATCH: 0.1.0
FEATURE_BRANCH_BUILD: 1

concurrency: release-${{ github.ref }}

name: protobuf-go-release

jobs:
publish:
name: "Publish"
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v3
- name: Version suffix
id: version_suffix
run: |
if [[ ${{ github.ref }} == "refs/heads/${{ github.event.repository.default_branch }}" ]]; then
echo 'for default branch pipeline'
USE=false
SUFFIX=''
EXTENSION=''
else
echo 'for feature branch pipeline'
USE=true
SUFFIX=${GITHUB_REF##*/}.${{env.FEATURE_BRANCH_BUILD}}
EXTENSION="-${SUFFIX}"
fi
echo 'use_version_suffix' $USE
echo 'version_suffix: ' $SUFFIX
echo "use_version_suffix=$USE" >> $GITHUB_OUTPUT
echo "version_suffix=$SUFFIX" >> $GITHUB_OUTPUT
echo "extension=$EXTENSION" >> $GITHUB_OUTPUT
- name : Semantic version
id: semantic_version
run: |
SEMANTIC_VERSION="${{ env.MAJOR_MINOR_PATCH }}"
SEMANTIC_VERSION="${SEMANTIC_VERSION}${{ steps.version_suffix.outputs.extension }}"
echo 'MAJOR_MINOR_PATCH: ' $MAJOR_MINOR_PATCH
echo 'SEMANTIC_VERSION: ' $SEMANTIC_VERSION
echo "semantic_version=$SEMANTIC_VERSION" >> $GITHUB_OUTPUT
echo "major_minor_patch=$MAJOR_MINOR_PATCH" >> $GITHUB_OUTPUT
- name: Create semantic versioning git tag for golang
uses: actions/github-script@v6
with:
script: |
github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: "refs/tags/v${{ steps.semantic_version.outputs.semantic_version }}",
sha: context.sha
})
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

# Go workspace file
go.work

# Downloaded proto compiler
internal/encoding/testing/protoc/*
11 changes: 11 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"[go]": {
"editor.insertSpaces": false,
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
"editor.suggest.snippetsPreventQuickSuggestions": false,
},
"go.lintOnSave": "package"
}
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<picture>
<img alt="Protobuf icon" src="/documentation/img/file-type-protobuf.svg" width="25%" height="25%">
</picture>
<picture>
<img alt="Heart" src="https://github.githubassets.com/images/icons/emoji/unicode/2764.png">
</picture>
<picture>
<img alt="Lua icon" src="/documentation/img/file-type-lua.svg" width="25%" height="25%">
</picture>
<picture>
<img alt="Heart" src="https://github.githubassets.com/images/icons/emoji/unicode/2764.png">
</picture>
<picture>
<img alt="Latex icon" src="/documentation/img/latex.svg" width="25%" height="25%">
</picture>

# Go related code for protocol buffers
This repository contains Go implementation for [protocol buffers](https://protobuf.dev).

The code design is based on [protocolbuffers/protobuf-go](https://github.com/protocolbuffers/protobuf-go).

## Packages
* [`encoding/protolua`]:
Package `protolua` converts protobuf messages to lua data types mostly using tables.
One way conversion is only supported for now.
Limited support of protobuf types because for it is only in usage in context of LuaTex.

# Status
[![protobuf-go-ci](https://github.com/KinNeko-De/protobuf-go/actions/workflows/ci.yml/badge.svg)](https://github.com/KinNeko-De/protobuf-go/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/KinNeko-De/protobuf-go/branch/main/graph/badge.svg?token=yvQYJ6kpYr)](https://codecov.io/gh/KinNeko-De/protobuf-go)

1 change: 1 addition & 0 deletions documentation/img/file-type-lua.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions documentation/img/file-type-protobuf.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions documentation/img/latex.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
220 changes: 220 additions & 0 deletions encoding/protolua/encode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
package protolua

import (
"errors"
"fmt"

"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
)

// Marshal converts the given proto.Message into a lua table using default options.
// Supports only proto3 messages currently.
func Marshal(m proto.Message) ([]byte, error) {
return LuaMarshalOption{}.Marshal(m)
}

type (
EncodingRun struct {
*Encoder
options LuaMarshalOption
}

MarshalFunc func(EncodingRun, protoreflect.Message) error

LuaMarshalOption struct {
Format struct {
// If set to false the output is formated as one line (default)
// If set to true the output is formated in multiple line with indent (better for humans)
Multiline bool
}

// Defines how the name of message are crated
// If set to nil, [protolua.JsonName] will be used
KeyName interface {
keyName(protoreflect.FieldDescriptor) string
}

// Additional Marshalers for non standard proto messages
// see [protolua.GoogleWellKnownTypesMarshaler] for an example
AdditionalMarshalers []interface {
Handle(fullName protoreflect.FullName) (MarshalFunc, error)
}
}
)

// Marshal convert the given proto.Message into a lua table using the given options.
func (o LuaMarshalOption) Marshal(m proto.Message) ([]byte, error) {
return o.marshal(m)
}

func (option LuaMarshalOption) marshal(m proto.Message) ([]byte, error) {
if m == nil {
return nil, errors.New("message can not be nil")
}

setDefaults(&option)
indent := ""
if option.Format.Multiline {
indent = DefaultIndent
}

encoder, err := NewEncoder(indent)
if err != nil {
return nil, err
}

encodingRun := EncodingRun{encoder, option}

bytes, err2 := marshalRootMessage(m.ProtoReflect(), encodingRun)
if err2 != nil {
return bytes, err2
}

return encodingRun.Encoder.Bytes(), nil
}

func setDefaults(option *LuaMarshalOption) {
if option.KeyName == nil {
option.KeyName = JsonName{}
}
}

func marshalRootMessage(m protoreflect.Message, encodingRun EncodingRun) ([]byte, error) {
// The json name is not populated, so the Protobuf name is used hereX
encodingRun.Encoder.WriteKey(string(m.Descriptor().Name()))

if err := encodingRun.marshalMessage(m); err != nil {
return nil, err
}
return nil, nil
}

// marshalMessage marshals the message and fields in the given protoreflect.Message.
func (e EncodingRun) marshalMessage(m protoreflect.Message) error {
if m.Descriptor().Syntax() != protoreflect.Proto3 {
return errors.New("only proto3 syntax is supported")
}

for _, marshaler := range e.options.AdditionalMarshalers {
marshalFunc, unsupportedTypeError := marshaler.Handle(m.Descriptor().FullName())
shouldReturn, returnValue := isHandledByOtherMarshaler(unsupportedTypeError, marshalFunc, e, m)
if shouldReturn {
return returnValue
}
}

marshalFunc, unsupportedTypeError := GoogleWellKnownTypesMarshaler{}.Handle(m.Descriptor().FullName())
shouldReturn, returnValue := isHandledByOtherMarshaler(unsupportedTypeError, marshalFunc, e, m)
if shouldReturn {
return returnValue
}

if !m.IsValid() {
e.Encoder.WriteNull()
return nil
}

var err error
e.Encoder.StartObject()
defer e.Encoder.EndObject()

fields := m.Descriptor().Fields()
upper := fields.Len()
for i := 0; i < upper; i++ {
currentField := fields.Get(i)
name := e.options.KeyName.keyName(currentField)

if err = e.Encoder.WriteKey(name); err != nil {
return err
}
if err = e.marshalValue(m.Get(currentField), currentField); err != nil {
return err
}
}

return err
}

func isHandledByOtherMarshaler(unsupportedTypeError error, marshalFunc MarshalFunc, e EncodingRun, m protoreflect.Message) (bool, error) {
if unsupportedTypeError != nil {
return true, unsupportedTypeError
}
if marshalFunc != nil {
return true, marshalFunc(e, m)
}
return false, nil
}

func (e EncodingRun) marshalValue(val protoreflect.Value, fd protoreflect.FieldDescriptor) error {
switch {
case fd.IsList():
return e.marshalList(val.List(), fd)
case fd.IsMap():
return e.marshalMap(val.Map(), fd)
default:
return e.marshalSingular(val, fd)
}
}

func (e EncodingRun) marshalSingular(val protoreflect.Value, fd protoreflect.FieldDescriptor) error {
if !val.IsValid() {
return nil
}

switch kind := fd.Kind(); kind {
case protoreflect.BoolKind:
e.WriteBool(val.Bool())

case protoreflect.StringKind:
if err := e.Encoder.WriteString(val.String()); err != nil {
return err
}
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind,
protoreflect.Uint32Kind, protoreflect.Fixed32Kind,
protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Uint64Kind,
protoreflect.Sfixed64Kind, protoreflect.Fixed64Kind:
e.Encoder.WriteNumber(val.String())

case protoreflect.FloatKind:
return errors.New("float is not supported yet")

case protoreflect.DoubleKind:
return errors.New("float is not supported yet")

case protoreflect.BytesKind:
return errors.New("byte is not supported yet")

case protoreflect.EnumKind:
return errors.New("enum is not supported yet")

case protoreflect.MessageKind, protoreflect.GroupKind:
if err := e.marshalMessage(val.Message()); err != nil {
return err
}

default:
panic(fmt.Sprintf("%v has unknown kind: %v", fd.FullName(), kind))
}
return nil
}

func (e EncodingRun) marshalList(list protoreflect.List, fd protoreflect.FieldDescriptor) error {

e.Encoder.StartArray()
defer e.Encoder.EndArray()

for i := 0; i < list.Len(); i++ {
item := list.Get(i)
e.Encoder.WriteIndexedList(i + 1)
if err := e.marshalSingular(item, fd); err != nil {
return err
}
}
return nil
}

func (e EncodingRun) marshalMap(mmap protoreflect.Map, fd protoreflect.FieldDescriptor) error {
return errors.New("maps are not supported yet")
}

0 comments on commit dbd7692

Please sign in to comment.