xrpcuri

package module
v0.0.0-...-ef8670c Latest Latest
Warning

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

Go to latest
Published: May 6, 2025 License: MIT Imports: 7 Imported by: 1

README

go-xrpcuri

Package xrpcuri provides an implementation of the XRPC-URI for BlueSky and its AT-Protocol, for the Go programming language.

XRPC is defined here: https://atproto.com/specs/xrpc

The XRPC-URI is not part of the https://atproto.com/specs/xrpc specification, but is instead something invented by this package.

Documention

Online documentation, which includes examples, can be found at: http://godoc.org/github.com/reiver/go-xrpcuri

GoDoc

XRPC URI Resolution

This package introduces two URI schemes for XRPC:

  • xrpc, and
  • xrpc-unencrypted.

For example:

  • xrpc://public.api.bsky.app/app.bsky.actor.getProfile?actor=reiver.bsky.social
  • xrpc-unencrypted://localhost/app.bsky.actor.getProfile?actor=reiver.bsky.social

Behind-the-scenes the scenes, these XRPC URI schemes are resolved to HTTPS, HTTP, WSS, and WS URLs.

MOST DEVELOPERS WHO ARE JUST MAKING XRPC CLIENT REQUESTS DO NOT HAVE TO WORRY ABOUT THE DETAILS OF THE RESOLUTION. IN THE SAME WAY THAT MOST DEVELOPERS DO NOT HAVE TO WORRY ABOUT HOW HTTP URIS ARE RESOLVED TO TCP.

How an XRPC URI gets resolved to an HTTPS, HTTP, WSS, or WS URL, depends on the XRPC request type.

Here are some examples:

XRPC URI XRPC Request Type Resolved URL
xrpc://example.com/app.cherry.fooBar execute https://example.com/xrpc/app.cherry.fooBar
xrpc://example.com/app.cherry.fooBar query https://example.com/xrpc/app.cherry.fooBar
xrpc://example.com/app.cherry.fooBar subscribe wss://example.com/xrpc/app.cherry.fooBar
xrpc-unencrypted://localhost/link.banana.bazQux execute http://localhost/xrpc/link.banana.bazQux
xrpc-unencrypted://localhost/link.banana.bazQux query http://localhost/xrpc/link.banana.bazQux
xrpc-unencrypted://localhost/link.banana.bazQux subscribe ws://localhost/xrpc/link.banana.bazQux

Import

To import package xrpcuri use import code like the follownig:

import "github.com/reiver/go-xrpcuri"

Installation

To install package xrpcuri do the following:

GOPROXY=direct go get https://github.com/reiver/go-xrpcuri

Author

Package xrpcuri was written by Charles Iliya Krempeaux

See Also

Documentation

Index

Examples

Constants

View Source
const (
	RequestTypeExecute   string = "execute" // an alternative name for "procedure" so that all the request-types can be a verb.
	RequestTypeProcedure string = "procedure"
	RequestTypeQuery     string = "query"
	RequestTypeSubscribe string = "subscribe"
)

Variables

This section is empty.

Functions

func Join

func Join(authority string, id string, query string, fragment string) string
Example
package main

import (
	"fmt"
	"net/url"

	"github.com/reiver/go-xrpcuri"
)

func main() {

	var authority string = "public.api.bsky.app"
	var id string = "app.bsky.actor.getProfile"
	var actor string = "reiver.bsky.social"

	query := "actor=" + url.QueryEscape(actor)

	uri := xrpcuri.Join(authority, id, query, "")

	fmt.Printf("authority:   %s\n", authority)
	fmt.Printf("id:          %s\n", id)
	fmt.Printf("actor:       %s\n", actor)
	fmt.Println()
	fmt.Printf("query: %s\n", query)
	fmt.Println()
	fmt.Printf("uri: %s\n", uri)

}
Output:
authority:   public.api.bsky.app
id:          app.bsky.actor.getProfile
actor:       reiver.bsky.social

query: actor=reiver.bsky.social

uri: xrpc://public.api.bsky.app/app.bsky.actor.getProfile?actor=reiver.bsky.social

func JoinUnencrypted

func JoinUnencrypted(authority string, id string, query string, fragment string) string

func Normalize

func Normalize(uri string) string

Normalize returns the normalized form of an XRPC-URI or an XRPC-unencrypted-URI.

Normalize does NOT validate the XRPC-URI or XRPC-unencrypted-URI. To validate, call Validate.

An example of a non-normalized XRPC-URI would be:

xrpc://VIDEO.archive.ORG/COM.Example.fooBar

Normalizing that non-normalized XRPC-URI would result in:

xrpc://video.archive.org/com.example.fooBar
Example
package main

import (
	"fmt"

	"github.com/reiver/go-xrpcuri"
)

func main() {

	var id string = "xrpcuri://Host.EXAMPLE/COM.Example.fooBar"

	normalizedID := xrpcuri.NormalizeID(id)

	fmt.Printf("original xrpc-uri:   %s\n", id)
	fmt.Printf("normalized xrpc-uri: %s\n", normalizedID)

}
Output:
original xrpc-uri:   xrpcuri://Host.EXAMPLE/COM.Example.fooBar
normalized xrpc-uri: xrpcuri://host.example/com.example.fooBar

func NormalizeAuthority

func NormalizeAuthority(authority string) string

NormalizeAuthority returns the normalized form of an XRPC-URI 'authority'.

The XRPC-URI 'authority' (such as "example.com") is part of an XRPC-URI (such as "xrpc://example.com").

In simple language, you can think of an XRPC-URI 'authority' as being either an Internet domain-name (ex: "example.com").

An example of a non-normalized XRPC-URI 'authority' would be "Example.COM". Normalizing that non-normalized XRPC-URI 'authority' would result in "example.com".

NormalizeAuthority will leave any potential 'userinfo' in the 'authority' as is.

Note that if you want to normalize a whole XRPC-URI rather than just an 'authority', then instead use Normalize.

Example
package main

import (
	"fmt"

	"github.com/reiver/go-xrpcuri"
)

func main() {

	var authority string = "Example.COM"

	normalizedAuthority := xrpcuri.NormalizeAuthority(authority)

	fmt.Printf("original authority:   %s\n", authority)
	fmt.Printf("normalized authority: %s\n", normalizedAuthority)

}
Output:
original authority:   Example.COM
normalized authority: example.com

func NormalizeID

func NormalizeID(id string) string

NormalizeID returns the normalized form of an XRPC-URI 'id', as defined in: https://atproto.com/specs/xrpc

An XRPC-URI 'id' is an NSID, as defined at: https://atproto.com/specs/nsid

The XRPC-URI 'id' (such as "com.example.fooBar") is part of an XRPC-URI (such as "xrpc://archive.org/com.example.fooBar").

An example of a non-normalized XRPC-URI 'id' would be "COM.Example.fooBar". Normalizing that non-normalized XRPC-URI 'id' would result in "com.example.fooBar".

Note that if you want to normalize a whole XRPC-URI rather than just a 'id', then instead use Normalize.

Example
package main

import (
	"fmt"

	"github.com/reiver/go-xrpcuri"
)

func main() {

	var id string = "COM.Example.fooBar"

	normalizedID := xrpcuri.NormalizeID(id)

	fmt.Printf("original id:   %s\n", id)
	fmt.Printf("normalized id: %s\n", normalizedID)

}
Output:
original id:   COM.Example.fooBar
normalized id: com.example.fooBar

func Resolve

func Resolve(uri string, requestType string) (string, error)

Resolve turns an XRPC-URI into an HTTP-URI.

For example:

"xrpc://public.api.bsky.app/app.bsky.actor.getProfile" -> "https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile"

"xrpc://example.com/com.example.fooBar" -> "https://example.com/xrpc/com.example.fooBar"

"xrpc-unencrypted://example.com/com.example.fooBar" -> "http://example.com/xrpc/com.example.fooBar"
Example
package main

import (
	"fmt"

	"github.com/reiver/go-xrpcuri"
)

func main() {

	var xrpcURI string = "xrpc://example.com/com.atproto.repo.listRecords"

	httpURI, err := xrpcuri.Resolve(xrpcURI, "query")
	if nil != err {
		fmt.Printf("ERROR: could not resolve XRPC-URI %q: %s\n", xrpcURI, err)
		return
	}

	fmt.Printf("http-uri: %s\n", httpURI)

}
Output:
http-uri: https://example.com/xrpc/com.atproto.repo.listRecords

func ResolveID

func ResolveID(id string) string

ResolveID resolves the 'id' (which is expected to be an NSID) into an HTTP path.

If `id` is "" then it returns "".

Example
package main

import (
	"fmt"

	"github.com/reiver/go-xrpcuri"
)

func main() {

	var id string = "app.bsky.actor.getProfile"

	path := xrpcuri.ResolveID(id)

	fmt.Printf("id:         %s\n", id)
	fmt.Printf("path: %s\n", path)

}
Output:
id:         app.bsky.actor.getProfile
path: /xrpc/app.bsky.actor.getProfile

func Split

func Split(uri string) (host string, id string, query string, fragment string, err error)

Split returns the 'host', 'id', 'query', and 'fragment' of at XRPC-URI or a XRPC-unencrypted-URI.

The 'id' should be an NSID (Namespaced Identifier).

For example:

var uri string = "xrpc://public.api.bsky.app/app.bsky.actor.getProfile?actor=reiver.bsky.social"

host, id, query, fragment, err := xrpcuri.Split(uri)
if nil != err {
	return err
}

// host     == "public.api.bsky.app"
// id       == "app.bsky.actor.getProfile"
// query    == "actor=reiver.bsky.social"
// fragment == ""

Split does NOT normalize the returned values.

If you are not sure whether to use Split or [SpliAndNormalize], use SplitAndNormalize.

func SplitAndNormalize

func SplitAndNormalize(uri string) (authority string, id string, query string, fragment string, err error)

SplitAndNormalize returns the 'authority', 'id', 'query', and 'fragment' of an XRPC-URI or XRPC-unencrypted-URI.

An 'id' is an NSID (Namespaced Identifier).

For example:

var uri string = "at://Host.EXAMPLE/COM.Example.foorBar"

authority, id, query, fragment, err := xrpcuri.SplitAndNormalize(uri)
if nil != err {
	return err
}

// authority == "host.example"
// id        == "com.example.foorBar"
// query     == ""
// fragment  == ""

SplitAndNormalize normalizes the returned values. If you are not sure whether to use Split or SpliAndNormalize, use SplitAndNormalize.

func SplitAndNormalizeAndValidate

func SplitAndNormalizeAndValidate(uri string) (authority string, id string, query string, fragment string, err error)

SplitAndNormalizeAndValidate returns the 'authority', 'id', 'query', and 'fragment' of an XRPC-URI or XRPC-unencrypted-URI.

An 'id' is an NSID (Namespaced Identifier).

For example:

var uri string = "at://Host.EXAMPLE/COM.Example.foorBar"

authority, id, query, fragment, err := xrpcuri.SplitAndNormalizeAndValidate(uri)
if nil != err {
	return err
}

// authority == "host.example"
// id        == "com.example.foorBar"
// query     == ""
// fragment  == ""

SplitAndNormalizeAndValidate normalizes the returned values.

If you are not sure whether to use Split or [SpliAndNormalize] or SplitAndNormalizeAndValidate, use SplitAndNormalizeAndValidate.

func Validate

func Validate(uri string) error

Validate returns an error if the XRPC-URI or XRPC-unencrypted-URI is invalid. It returns nil if the XRPC-URI is valid.

To validate a potential XRPC-URI or XRPC-unencrypted-URI more liberally, use one of the following: ValidatePrefix, ValidateScheme.

Example
package main

import (
	"fmt"

	"github.com/reiver/go-xrpcuri"
)

func main() {

	var xrpcURI string = "xrpc://archive.org/COM.Example.fooBar?actor=JoeBlow"

	err := xrpcuri.Validate(xrpcURI)

	fmt.Printf("XRPC-URI: %s\n", xrpcURI)
	fmt.Printf("validation error: %s\n", err)

}
Output:
XRPC-URI: xrpc://archive.org/COM.Example.fooBar?actor=JoeBlow
validation error: xrpcuri: XRPC-URI "xrpc://archive.org/COM.Example.fooBar?actor=JoeBlow" has an id "COM.Example.fooBar" that is not a valid NSID: nsid: character №0 ('C') (U+0043) of domain-authority part №0 ("COM") of domain-authority ("COM.Example") of nsid ("COM.Example.fooBar") is not a digit ('0'-'9'), lower-case letter ('a'-'z'), or a hyphen ('-')

func ValidateAuthority

func ValidateAuthority(authority string) error

ValidateAuthority returns an error if the XRPC-URI 'authority' is invalid. It returns nil if the XRPC-URI authority is valid.

NOTE THAT THIS IS NOT VALIDATING AN XRPC-URI, BUT IS INSTEAD VALIDATING AN XRPC-URI 'authority'.

Example
package main

import (
	"fmt"

	"github.com/reiver/go-xrpcuri"
)

func main() {

	var authority string = "user:pass@archive.org"

	err := xrpcuri.ValidateAuthority(authority)

	fmt.Printf("authority: %s\n", authority)
	fmt.Printf("validation error: %s\n", err)

}
Output:
authority: user:pass@archive.org
validation error: xrpcuri: authority may not have an "@" in it

func ValidateID

func ValidateID(id string) error

ValidateID returns an error if the XRPC-URI 'id' is invalid. It returns nil if the XRPC-URI id is valid.

NOTE THAT THIS IS NOT VALIDATING AN XRPC-URI, BUT IS INSTEAD VALIDATING AN XRPC-URI 'id'.

An XRPC-URI 'id' must be an NSID, and its validation rules are defined at: https://atproto.com/specs/nsid

Example
package main

import (
	"fmt"

	"github.com/reiver/go-xrpcuri"
)

func main() {

	var id string = "COM.Example.fooBar"

	err := xrpcuri.ValidateID(id)

	fmt.Printf("id (i.e., NSID): %s\n", id)
	fmt.Printf("validation error: %s\n", err)

}
Output:
id (i.e., NSID): COM.Example.fooBar
validation error: nsid: character №0 ('C') (U+0043) of domain-authority part №0 ("COM") of domain-authority ("COM.Example") of nsid ("COM.Example.fooBar") is not a digit ('0'-'9'), lower-case letter ('a'-'z'), or a hyphen ('-')

func ValidatePrefix

func ValidatePrefix(uri string) error

ValidatePrefix only validates the prefix (i.e., "xrpc://" or "xrpc-unencrypted://") of a potential XRPC-URI or XRPC-unencrypted-URI.

So, it checks to see if the URI starts with "xrpc://" or "xrpc-unencrypted://". And, that is it.

You would use ValidatePrefix if you wanted to be very liberal in what you accept as a valid XRPC-URI or XRPC-unencrypted-URI, including accepting an empty 'authority' / 'host'. I.e., this does a bit more than ValidatePrefix.

ValidatePrefix calls ValidateScheme internally.

For a more thorough validation of the whole XRPC-URI or XRPC-unencrypted-URI instead use Validate.

Example
package main

import (
	"fmt"

	"github.com/reiver/go-xrpcuri"
)

func main() {

	var uri string = "xrpc:example.com"

	err := xrpcuri.ValidatePrefix(uri)

	fmt.Printf("error: %s\n", err)

}
Output:
error: xrpcuri: URI "xrpc:example.com" is not an XRPC-URI or an XRPC-unencrypted-URI because it does not begin with "xrpc://" or "xrpc-unencrypted://"

func ValidateScheme

func ValidateScheme(uri string) error

ValidateScheme only validates the scheme of a potential XRPC-URI or XRPC-unencrypted-URI.

So, it checks to see if the URI starts with "xrpc:" or "xrpc-unencrypted:". And, that is it.

You would use ValidateScheme if you wanted to be very liberal in what you accept as a valid XRPC-URI or XRPC-unencrypted-URI, including not caring if the XRPC-URI or XRPC-unencrypted-URI has a 'authority' / 'host' or not. I.e., this is the minimum amount of validation you can do to validate an XRPC-URI or an XRPC-unencrypted-URI.

Note that you are passing ValidateScheme the whole URI, and not just the scheme.

For a more thorough validation of the whole XRPC-URI pr XRPC-unencrypted-URI instead use Validate.

Alternatively, to validate a bit more than ValidateScheme, without being as thorough as Validate, instead use ValidatePrefix.

Example
package main

import (
	"fmt"

	"github.com/reiver/go-xrpcuri"
)

func main() {

	var uri string = "http://example.com/once/twice/thrice/fource.html"

	err := xrpcuri.ValidateScheme(uri)

	fmt.Printf("error: %s\n", err)

}
Output:
error: xrpcuri: URI "http://example.com/once/twice/thrice/fource.html" is not an XRPC-URI or an XRPC-unencrypted-URI because it does not begin with "xrpc:" or "xrpc-unencrypted:"

Types

type URL

type URL struct {
	Unencrypted bool
	Host        string
	NSID        string
	Query       string
}

URL represents an 'xrpc' and 'xrpc-unencrypted' URL.

For example:

xrpc://public.api.bsky.app/app.bsky.actor.getProfile?actor=reiver.bsky.social

xrpc://example.com/com.example.fooBar

xrpc-unencrypted://example.com/com.example.fooBar

func ConstructURL

func ConstructURL(host string, nsid string, query string) URL

ConstructURL constructs an XRPC URL (i.e., "xrpc://...") from a 'host', an 'nsid', and a 'query'.

The 'query' can be an empty string.

func ConstructUnencryptedURL

func ConstructUnencryptedURL(host string, nsid string, query string) URL

ConstructURL constructs an XRPC URL (i.e., "xrpc-unencrypted://...") from a 'host', an 'nsid', and a 'query'.

The 'query' can be an empty string.

func MustParseURL

func MustParseURL(url string) URL

MustParseURL is similar to ParseURL except that it panic()s if there is an error (rather than returning it, like how ParseURL does).

func ParseURL

func ParseURL(url string) (URL, error)

func (URL) MustResolve

func (receiver URL) MustResolve(requestType string) string

MustResolve is similar to Resolve except that it panic()s if there is an error (rather than returning it, like how Resolve does).

func (URL) Resolve

func (receiver URL) Resolve(requestType string) (string, error)

Resolve turns an XRPC URL into an HTTP URL.

For example:

"xrpc://public.api.bsky.app/app.bsky.actor.getProfile" -> "https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile"

"xrpc://example.com/com.example.fooBar" -> "https://example.com/xrpc/com.example.fooBar"

"xrpc-unencrypted://example.com/com.example.fooBar" -> "http://example.com/xrpc/com.example.fooBar"

func (URL) String

func (receiver URL) String() string

String returns the (serialized) XRPC URL.

func (URL) Validate

func (receiver URL) Validate() error

Validate returns an error if the URL is invalid.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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