resourcenames

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 20, 2026 License: MIT Imports: 4 Imported by: 0

README

resourcename

Google's AIP defines standards on how APIs should be written. AIP-122 mandates that each resource must be have a resource name, by which the resource is identified. That name must be of the format resources/{resource_id}. In the domain layer however, your domain resources will typically work with id only, rather than the full resource name. Parsing the id(s) from a resource name can be tedious and repetitive. With this package, they can be injected into a resource from a pattern and a resource name using golang struct tags.

Problem Example

This is how the AIP defines api resources (example from AIP). The primary identifier is not an id, but a full resource name (/publishers/{publisher}/books/{book}), including parent resource name and all.

message Book {
  option (google.api.resource) = {
    type: "library.googleapis.com/Book"
    pattern: "publishers/{publisher}/books/{book}"
  };

  // The resource name of the book.
  // Format: publishers/{publisher}/books/{book}
  string name = 1 [(google.api.field_behavior) = IDENTIFIER];

  // Other fields...
}

Also, all requests identify the resource by it's full resource name:

message GetBookRequest {
  // The name of the book to retrieve.
  // Format: publishers/{publisher}/books/{book}
  string name = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.resource_reference) = {
      type: "library.googleapis.com/Book"
    }];
}

The resource may also have a parent (or even grandparent) resource. It's resource name can become very "nested".

grandparents/{grandparent_id}/parents/{parent_id}/children/{child_id}

But generally, our domain models just work with the ids. We don't pass full resource names as primary keys to the database, for example. This package help translating api messages to domain layer objects by unmarshaling resource names into tagged domain structs.

Usage

When defining a domain resource, you can use golang field tags to specify a resource name segment (rns) for this field. For now, only string fields are supported.

type Resource struct {
    Id string `rns:"resource_id"`
}

You will also need to define the pattern of your resource's resource name. The variable segments of the resource name is what we will map the tagged fields to. Make sure, that you wrap variable segments in curly braces and that the name matches what you set in the tag.

pattern := resourcename.FromPattern("/resources/{resource_id}")

We can use our pattern to parse a string resource name and assign it's variable segments to our resource struct. Make sure you pass your resource by reference.

resource := Resource{}
err := pattern.Unmarshal("/resources/abcdefg", &resource)

Of course we can also do the reverse

resource := Resource {
  Id: "abcdefg"
}

pattern := resourcename.FromPattern("/resources/{resource_id}")
resourceName, err := pattern.Marshal(&resource)
if err != nil {
  // ...
}

We also support any number of parent resources. Usually you don't have grandparent ids in your model, but this is just to prove that it would be supported. But our field tags would look like:

type Child struct {
    Id            string `rns:"child_id"`
    ParentId      string `rns:"parent_id"`
    
    // this may not make sense if parent has the reference to grandparent
    // but works if need be
    GrandparentId string `rns:"grandparent_id"` 
}

We can unmarshal a child resource just like any other resource.

child := Child{}

rname := "grandparents/abcd/parents/efgh/children/ijkl"

pattern := resourcenames.FromPattern(
    "grandparents/{grandparent_id}/parents/{parent_id}/children/{child_id}")

err := pattern.Unmarshal(rname, &child)
if err != nil {
    return err
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrSegmentLengthMismatch   = errors.New("resource name segment count does not match that of the pattern")
	ErrSegmentConstantMismatch = errors.New("a non-variable segment did not match constant pattern segment")
	ErrNotAStruct              = errors.New("resource must be a pointer to a struct")
	ErrMissingSegment          = errors.New("resource is missing variable segment")
)

Functions

This section is empty.

Types

type NamePattern

type NamePattern struct {
	// contains filtered or unexported fields
}

Represents all segments of a resource name pattern. It is a template to parse resource names against and extract the values of variable segments.

func FromPattern

func FromPattern(pattern string) NamePattern

FromPattern constructs a NamePattern from a given resource name pattern string. Given a pattern string like `/resource/{resource_id}`, it constructs a NamePattern consisting of the constant segment `resource` and the variable segment `resource_id`

func (NamePattern) Marshal added in v0.1.0

func (p NamePattern) Marshal(resource any) (string, error)

func (NamePattern) Parse

func (p NamePattern) Parse(resourcename string) (map[string]string, error)

Parse() parses all variable segments of a pattern from a given resource name into a map[string]string, where key is the name of the variable segment and value is the value of that segment in the resource name.

func (NamePattern) Unmarshal added in v0.1.0

func (p NamePattern) Unmarshal(resourceName string, resource any) error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL