Skip to content

Commit

Permalink
Resource implementation (#54)
Browse files Browse the repository at this point in the history
* Resource implementation.

Protocol Buffer representation.
JSONPB implementation
Resource management: registration, building, resolving
Default implementation of UUID resource management.
Default implementation of External resource management.

* Updated: protobuf package to v1.1.0

* UT for "internal" package

Signed-off-by: Andrei Maskalenka <amaskalenka@infoblox.com>

* Implementation of jsonpb.JSONPBMarshale and jsonpb.JSONPBUnmarshaler
interfaces. UTs

* Package "resource" implementation and UTs.

* External identifier: implementation + UTs

* UUID Internal resource: implementation + UT.

* External resource: UT fixed

* README, testable examples

* Renamed "external" package to "fq" since it more clearly describe
intention.

* README: fixed typo

* New version.

* integer and uuid codecs bypass empty app name and resource type

* Godep

* resource.Nil is a value now

* Default integer

* driver.Value implementation

* JSONBP marshaling

* gorm: resource implementation + UT

* infoblox.rpc -> atlas.rpc

* infoblox.rpc -> atlas.rpc

* README

* Default codecs

* empty string to 0

* nil dereference

* Probably it is finish

* infoblox.rpc -> atlas.rpc
  • Loading branch information
amaskalenka authored Jun 25, 2018
1 parent 9d03e37 commit 1498dd0
Show file tree
Hide file tree
Showing 16 changed files with 1,364 additions and 80 deletions.
7 changes: 4 additions & 3 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

131 changes: 131 additions & 0 deletions gorm/resource/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Description

The common interfaces, types and helper functions to work with identifiers are defined in [resource](resource.go) file.
Please see [example](example_test.go) to see how to work with identifiers.

# Codecs

You could register `resource.Codec` for you PB type to be used to convert `atlas.rpc.Identifier` that defined for that type.

By default if PB resource is undefined (`nil`) the `atlas.rpc.Identifier` is converted to a string in fully qualified format specified for
Atlas References, otherwise the Resource ID part is returned as string value.

If `resource.Codec` is not registered for a PB type the value of identifier is converted from `driver.Value` to a string.
If Resource Type is not found it is populated from the name of PB type,
the Application Name is populated if was registered. (see `RegisterApplication`).

# protoc-gen-gorm

The plugin has support of `atlas.rpc.Identifier` for **all** association types. All you need is to define your Primary/Foreign keys as
a fields of `atlas.rpc.Identifier` type.

In the `XxxORM` generated models `atlas.rpc.Identifier` will be replaced to the type specified in `(gorm.field).tag` option.
The only numeric and text formats are supported. If type is not set it will be generated as `interface{}`.

If you want to expose foreign keys on API just leave them with empty type in `gorm.field.tag` and it will be calculated based on the
parent's primary key type.

The Postgres types from tags are converted as follows:

```go
switch type_from_tag {
case "uuid", "text", "char", "array", "cidr", "inet", "macaddr":
orm_model_field = "string"
case "smallint", "integer", "bigint", "numeric", "smallserial", "serial", "bigserial":
orm_model_field = "int64"
case "jsonb", "bytea":
orm_model_field = "[]byte"
case "":
orm_model_field = "interface{}"
default:
return errors.New("unknown tag type of atlas.rpc.Identifier")
}
```

**NOTE** Be sure to set type properly for association fields.

**Step 1** Let's define PB resources

```proto
syntax = "proto3";
import "github.com/infobloxopen/atlas-app-toolkit/rpc/resource/resource.proto";
import "github.com/infobloxopen/protoc-gen-gorm/options/gorm.proto";
option go_package = "github.com/yourapp/pb;pb";
message A {
option (gorm.opts).ormable = true;
atlas.rpc.Identifier id = 1 [(gorm.field).tag = {type: "integer"}];
string value = 2;
repeated B b_list = 3; // has many
atlas.rpc.Identifier external = 4 [(gorm.field).tag = {type: "text"}];
}
message B {
option (gorm.opts).ormable = true;
atlas.rpc.Identifier id = 1 [(gorm.field).tag = {type: "integer"}];
string value = 2;
// foreign key to A parent. !!! Will be set to the type of A.id
atlas.rpc.Identifier a_id = 3;
}
```

In JSON it could look like
```json
{
"id": "someapp/resource:a/1",
"value": "someAvalue",
"b_list": [
{
"id": "someapp/resource:b/1",
"value": "someBvalue",
"a_id": "someapp/resource:a/1"
}
]
}
```

The generated code could be:
```go
import "github.com/infobloxopen/atlas-app-toolkit/gorm/resource"

type AORM struct {
Id int64
Value string
BList []*BORM
External string
}

type BORM struct {
Id int64
Value string
AId *int64
}
```

**Step 2** The last thing you need to make identifiers work correctly is to register the name of your application,
that name will be used during encoding to populate ApplicationName field of `atlas.rpc.Identifier`.

```go
package main

import "github.com/infobloxopen/atlas-app-toolkit/gorm/resource"

func main() {
resource.RegisterApplication("MyAppName")
}
```

# FAQ

## How to customize name of my PB type?

Add `XXX_MessageName() string` method to you PB type. See [Name](resource.go) function.

## I want to validate/generate atlas.rpc.Identifier for PB types in my application

Implement [resource.Codec](resource.go) for your PB types and you will be given a full control on how `Identifier`s
are converted to/from `driver.Value`.
122 changes: 122 additions & 0 deletions gorm/resource/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package resource

import (
"fmt"

resourcepb "github.com/infobloxopen/atlas-app-toolkit/rpc/resource"
)

type ExampleGoType struct {
ID int64
ExternalID string
VarName string
}

type ExampleProtoMessage struct {
Id *resourcepb.Identifier
ExternalId *resourcepb.Identifier
VarName string
}

func (ExampleProtoMessage) XXX_MessageName() string { return "ExampleProtoMessage" }
func (ExampleProtoMessage) Reset() {}
func (ExampleProtoMessage) String() string { return "ExampleProtoMessage" }
func (ExampleProtoMessage) ProtoMessage() {}

func Example() {
RegisterApplication("app")

// you want to convert PB type to your application type
toGoTypeFunc := func(msg *ExampleProtoMessage) (*ExampleGoType, error) {
var v ExampleGoType

// arbitrary variables
v.VarName = msg.VarName

// convert RPC identifier using UUID Codec for ExampleProtoMessage resource type
if id, err := DecodeInt64(msg, msg.Id); err != nil {
return nil, err
} else {
v.ID = id
}
// convert RPC identifier using External Codec for default resource type
if id, err := Decode(nil, msg.ExternalId); err != nil {
return nil, err
} else {
v.ExternalID = id.(string)
}
return &v, nil
}

// let's create PB message
pb := &ExampleProtoMessage{
Id: &resourcepb.Identifier{
ApplicationName: "simpleapp",
ResourceType: "examples",
ResourceId: "12",
},
// ExternalId stores data about "external_resource" that belongs to
// "externalapp" and has id "id"
ExternalId: &resourcepb.Identifier{
ApplicationName: "externalapp",
ResourceType: "external_resource",
ResourceId: "id",
},
VarName: "somename",
}

val, err := toGoTypeFunc(pb)
if err != nil {
fmt.Printf("failed to convert TestProtoMessage to TestGoType: %s\n", err)
return
}

fmt.Printf("application name of integer id: %v\n", val.ID)
fmt.Printf("application name of fqstring id: %v\n", val.ExternalID)

// so now you want to convert it back to PB representation
toPBMessageFunc := func(v *ExampleGoType) (*ExampleProtoMessage, error) {
var pb ExampleProtoMessage

// arbitrary variables
pb.VarName = v.VarName

// convert internal id to RPC representation using registered UUID codec
if id, err := EncodeInt64(pb, v.ID); err != nil {
return nil, err
} else {
pb.Id = id
}

// convert fqstring id to RPC representation using registered External codec
if id, err := Encode(nil, v.ExternalID); err != nil {
return nil, err
} else {
pb.ExternalId = id
}

return &pb, nil
}

pb, err = toPBMessageFunc(val)
if err != nil {
fmt.Println("failed to convert TestGoType to TestProtoMessage")
}

fmt.Printf("application name of internal id: %s\n", pb.Id.GetApplicationName())
fmt.Printf("resource type of internal id: %s\n", pb.Id.GetResourceType())
fmt.Printf("resource id of internal id: %s\n", pb.Id.GetResourceId())
fmt.Printf("application name of fqstring id: %s\n", pb.ExternalId.GetApplicationName())
fmt.Printf("resource type of fqstring id: %s\n", pb.ExternalId.GetResourceType())
fmt.Printf("resource id of fqstring id: %s\n", pb.ExternalId.GetResourceId())

// Output:
//application name of integer id: 12
//application name of fqstring id: externalapp/external_resource/id
//application name of internal id: app
//resource type of internal id: exampleprotomessage
//resource id of internal id: 12
//application name of fqstring id: externalapp
//resource type of fqstring id: external_resource
//resource id of fqstring id: id
}
Loading

0 comments on commit 1498dd0

Please sign in to comment.