-
Notifications
You must be signed in to change notification settings - Fork 115
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
9d03e37
commit 1498dd0
Showing
16 changed files
with
1,364 additions
and
80 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.