Add relayinfo package and utility modules for NIP-11 support
Introduce the `relayinfo` package with `NIP-11` utilities, including `Fees`, `Limits`, and `NIPs` structures. Add utility modules for handling numbers, timestamps, and kinds. Integrate functionality for fetching and managing relay information.
This commit is contained in:
424
pkg/encoders/kind/kind.go
Normal file
424
pkg/encoders/kind/kind.go
Normal file
@@ -0,0 +1,424 @@
|
||||
// Package kind includes a type for convenient handling of event kinds, and a
|
||||
// kind database with reverse lookup for human-readable information about event
|
||||
// kinds.
|
||||
package kind
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"lol.mleku.dev/chk"
|
||||
"next.orly.dev/pkg/encoders/ints"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
// K - which will be externally referenced as kind.K is the event type in the
|
||||
// nostr protocol, the use of the capital K signifying type, consistent with Go
|
||||
// idiom, the Go standard library, and much, conformant, existing code.
|
||||
type K struct {
|
||||
K uint16
|
||||
}
|
||||
|
||||
// New creates a new kind.K with a provided integer value. Note that anything larger than 2^16
|
||||
// will be truncated.
|
||||
func New[V constraints.Integer](k V) (ki *K) { return &K{uint16(k)} }
|
||||
|
||||
// ToInt returns the value of the kind.K as an int.
|
||||
func (k *K) ToInt() int {
|
||||
if k == nil {
|
||||
return 0
|
||||
}
|
||||
return int(k.K)
|
||||
}
|
||||
|
||||
// ToU16 returns the value of the kind.K as an uint16 (the native form).
|
||||
func (k *K) ToU16() uint16 {
|
||||
if k == nil {
|
||||
return 0
|
||||
}
|
||||
return k.K
|
||||
}
|
||||
|
||||
// ToI32 returns the value of the kind.K as an int32.
|
||||
func (k *K) ToI32() int32 {
|
||||
if k == nil {
|
||||
return 0
|
||||
}
|
||||
return int32(k.K)
|
||||
}
|
||||
|
||||
// ToU64 returns the value of the kind.K as an uint64.
|
||||
func (k *K) ToU64() uint64 {
|
||||
if k == nil {
|
||||
return 0
|
||||
}
|
||||
return uint64(k.K)
|
||||
}
|
||||
|
||||
// Name returns the human readable string describing the semantics of the kind.K.
|
||||
func (k *K) Name() string { return GetString(k) }
|
||||
|
||||
// Equal checks if
|
||||
func (k *K) Equal(k2 *K) bool {
|
||||
if k == nil || k2 == nil {
|
||||
return false
|
||||
}
|
||||
return k.K == k2.K
|
||||
}
|
||||
|
||||
var Privileged = []*K{
|
||||
EncryptedDirectMessage,
|
||||
GiftWrap,
|
||||
GiftWrapWithKind4,
|
||||
JWTBinding,
|
||||
ApplicationSpecificData,
|
||||
Seal,
|
||||
PrivateDirectMessage,
|
||||
}
|
||||
|
||||
// IsPrivileged returns true if the type is the kind of message nobody else than the pubkeys in
|
||||
// the event and p tags of the event are party to.
|
||||
func (k *K) IsPrivileged() (is bool) {
|
||||
for i := range Privileged {
|
||||
if k.Equal(Privileged[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Marshal renders the kind.K into bytes containing the ASCII string form of the kind number.
|
||||
func (k *K) Marshal(dst []byte) (b []byte) { return ints.New(k.ToU64()).Marshal(dst) }
|
||||
|
||||
// Unmarshal decodes a byte string into a kind.K.
|
||||
func (k *K) Unmarshal(b []byte) (r []byte, err error) {
|
||||
n := ints.New(0)
|
||||
if r, err = n.Unmarshal(b); chk.T(err) {
|
||||
return
|
||||
}
|
||||
k.K = n.Uint16()
|
||||
return
|
||||
}
|
||||
|
||||
// GetString returns a human-readable identifier for a kind.K.
|
||||
func GetString(t *K) string {
|
||||
if t == nil {
|
||||
return ""
|
||||
}
|
||||
MapMx.Lock()
|
||||
defer MapMx.Unlock()
|
||||
return Map[t.K]
|
||||
}
|
||||
|
||||
// IsEphemeral returns true if the event kind is an ephemeral event. (not to be
|
||||
// stored)
|
||||
func (k *K) IsEphemeral() bool {
|
||||
return k.K >= EphemeralStart.K && k.K < EphemeralEnd.K
|
||||
}
|
||||
|
||||
// IsReplaceable returns true if the event kind is a replaceable kind - that is,
|
||||
// if the newest version is the one that is in force (eg follow lists, relay
|
||||
// lists, etc.
|
||||
func (k *K) IsReplaceable() bool {
|
||||
return k.K == ProfileMetadata.K || k.K == FollowList.K ||
|
||||
(k.K >= ReplaceableStart.K && k.K < ReplaceableEnd.K)
|
||||
}
|
||||
|
||||
// IsParameterizedReplaceable is a kind of event that is one of a group of
|
||||
// events that replaces based on matching criteria.
|
||||
func (k *K) IsParameterizedReplaceable() bool {
|
||||
return k.K >= ParameterizedReplaceableStart.K &&
|
||||
k.K < ParameterizedReplaceableEnd.K
|
||||
}
|
||||
|
||||
// Directory events are events that necessarily need to be readable by anyone in
|
||||
// order to interact with users who have access to the relay, in order to
|
||||
// facilitate other users to find and interact with users on an auth-required
|
||||
// relay.
|
||||
var Directory = []*K{
|
||||
ProfileMetadata,
|
||||
FollowList,
|
||||
EventDeletion,
|
||||
Reporting,
|
||||
RelayListMetadata,
|
||||
MuteList,
|
||||
DMRelaysList,
|
||||
}
|
||||
|
||||
// IsDirectoryEvent returns whether an event kind is a Directory event, which
|
||||
// should grant permission to read such events without requiring authentication.
|
||||
func (k *K) IsDirectoryEvent() bool {
|
||||
for i := range Directory {
|
||||
if k.Equal(Directory[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
// ProfileMetadata is an event type that stores user profile data, pet
|
||||
// names, bio, lightning address, etc.
|
||||
ProfileMetadata = &K{0}
|
||||
// SetMetadata is a synonym for ProfileMetadata.
|
||||
SetMetadata = &K{0}
|
||||
// TextNote is a standard short text note of plain text a la twitter
|
||||
TextNote = &K{1}
|
||||
// RecommendServer is an event type that...
|
||||
RecommendServer = &K{2}
|
||||
RecommendRelay = &K{2}
|
||||
// FollowList an event containing a list of pubkeys of users that should be
|
||||
// shown as follows in a timeline.
|
||||
FollowList = &K{3}
|
||||
Follows = &K{3}
|
||||
// EncryptedDirectMessage is an event type that...
|
||||
EncryptedDirectMessage = &K{4}
|
||||
// Deletion is an event type that...
|
||||
Deletion = &K{5}
|
||||
EventDeletion = &K{5}
|
||||
// Repost is an event type that...
|
||||
Repost = &K{6}
|
||||
// Reaction is an event type that...
|
||||
Reaction = &K{7}
|
||||
// BadgeAward is an event type
|
||||
BadgeAward = &K{8}
|
||||
// Seal is an event that wraps a PrivateDirectMessage and is placed inside a
|
||||
// GiftWrap or GiftWrapWithKind4
|
||||
Seal = &K{13}
|
||||
// PrivateDirectMessage is a nip-17 direct message with a different
|
||||
// construction. It doesn't actually appear as an event a relay might receive
|
||||
// but only as the stringified content of a GiftWrap or GiftWrapWithKind4 inside
|
||||
// a
|
||||
PrivateDirectMessage = &K{14}
|
||||
// ReadReceipt is a type of event that marks a list of tagged events (e
|
||||
// tags) as being seen by the client, its distinctive feature is the
|
||||
// "expiration" tag which indicates a time after which the marking expires
|
||||
ReadReceipt = &K{15}
|
||||
// GenericRepost is an event type that...
|
||||
GenericRepost = &K{16}
|
||||
// ChannelCreation is an event type that...
|
||||
ChannelCreation = &K{40}
|
||||
// ChannelMetadata is an event type that...
|
||||
ChannelMetadata = &K{41}
|
||||
// ChannelMessage is an event type that...
|
||||
ChannelMessage = &K{42}
|
||||
// ChannelHideMessage is an event type that...
|
||||
ChannelHideMessage = &K{43}
|
||||
// ChannelMuteUser is an event type that...
|
||||
ChannelMuteUser = &K{44}
|
||||
// Bid is an event type that...
|
||||
Bid = &K{1021}
|
||||
// BidConfirmation is an event type that...
|
||||
BidConfirmation = &K{1022}
|
||||
// OpenTimestamps is an event type that...
|
||||
OpenTimestamps = &K{1040}
|
||||
GiftWrap = &K{1059}
|
||||
GiftWrapWithKind4 = &K{1060}
|
||||
// FileMetadata is an event type that...
|
||||
FileMetadata = &K{1063}
|
||||
// LiveChatMessage is an event type that...
|
||||
LiveChatMessage = &K{1311}
|
||||
// BitcoinBlock is an event type created for the Nostrocket
|
||||
BitcoinBlock = &K{1517}
|
||||
// LiveStream from zap.stream
|
||||
LiveStream = &K{1808}
|
||||
// ProblemTracker is an event type used by Nostrocket
|
||||
ProblemTracker = &K{1971}
|
||||
// MemoryHole is an event type contains a report about an event (usually
|
||||
// text note or other human readable)
|
||||
MemoryHole = &K{1984}
|
||||
Reporting = &K{1984}
|
||||
// Label is an event type has L and l tags, namespace and type - NIP-32
|
||||
Label = &K{1985}
|
||||
// CommunityPostApproval is an event type that...
|
||||
CommunityPostApproval = &K{4550}
|
||||
JobRequestStart = &K{5000}
|
||||
JobRequestEnd = &K{5999}
|
||||
JobResultStart = &K{6000}
|
||||
JobResultEnd = &K{6999}
|
||||
JobFeedback = &K{7000}
|
||||
ZapGoal = &K{9041}
|
||||
// ZapRequest is an event type that...
|
||||
ZapRequest = &K{9734}
|
||||
// Zap is an event type that...
|
||||
Zap = &K{9735}
|
||||
Highlights = &K{9882}
|
||||
// ReplaceableStart is an event type that...
|
||||
ReplaceableStart = &K{10000}
|
||||
// MuteList is an event type that...
|
||||
MuteList = &K{10000}
|
||||
BlockList = &K{10000}
|
||||
// PinList is an event type that...
|
||||
PinList = &K{10001}
|
||||
// RelayListMetadata is an event type that...
|
||||
RelayListMetadata = &K{10002}
|
||||
BookmarkList = &K{10003}
|
||||
CommunitiesList = &K{10004}
|
||||
PublicChatsList = &K{10005}
|
||||
BlockedRelaysList = &K{10006}
|
||||
SearchRelaysList = &K{10007}
|
||||
InterestsList = &K{10015}
|
||||
UserEmojiList = &K{10030}
|
||||
DMRelaysList = &K{10050}
|
||||
FileStorageServerList = &K{10096}
|
||||
// JWTBinding is an event kind that creates a link between a JWT certificate and a pubkey
|
||||
JWTBinding = &K{13004}
|
||||
// NWCWalletServiceInfo is an event type that...
|
||||
NWCWalletServiceInfo = &K{13194}
|
||||
WalletServiceInfo = &K{13194}
|
||||
// ReplaceableEnd is an event type that...
|
||||
ReplaceableEnd = &K{19999}
|
||||
// EphemeralStart is an event type that...
|
||||
EphemeralStart = &K{20000}
|
||||
LightningPubRPC = &K{21000}
|
||||
// ClientAuthentication is an event type that...
|
||||
ClientAuthentication = &K{22242}
|
||||
// NWCWalletRequest is an event type that...
|
||||
NWCWalletRequest = &K{23194}
|
||||
WalletRequest = &K{23194}
|
||||
// NWCWalletResponse is an event type that...
|
||||
NWCWalletResponse = &K{23195}
|
||||
WalletResponse = &K{23195}
|
||||
NWCNotification = &K{23196}
|
||||
WalletNotificationNip4 = &K{23196}
|
||||
WalletNotification = &K{23197}
|
||||
// NostrConnect is an event type that...
|
||||
NostrConnect = &K{24133}
|
||||
HTTPAuth = &K{27235}
|
||||
// EphemeralEnd is an event type that...
|
||||
EphemeralEnd = &K{29999}
|
||||
// ParameterizedReplaceableStart is an event type that...
|
||||
ParameterizedReplaceableStart = &K{30000}
|
||||
// CategorizedPeopleList is an event type that...
|
||||
CategorizedPeopleList = &K{30000}
|
||||
FollowSets = &K{30000}
|
||||
// CategorizedBookmarksList is an event type that...
|
||||
CategorizedBookmarksList = &K{30001}
|
||||
GenericLists = &K{30001}
|
||||
RelaySets = &K{30002}
|
||||
BookmarkSets = &K{30003}
|
||||
CurationSets = &K{30004}
|
||||
// ProfileBadges is an event type that...
|
||||
ProfileBadges = &K{30008}
|
||||
// BadgeDefinition is an event type that...
|
||||
BadgeDefinition = &K{30009}
|
||||
InterestSets = &K{30015}
|
||||
// StallDefinition creates or updates a stall
|
||||
StallDefinition = &K{30017}
|
||||
// ProductDefinition creates or updates a product
|
||||
ProductDefinition = &K{30018}
|
||||
MarketplaceUIUX = &K{30019}
|
||||
ProductSoldAsAuction = &K{30020}
|
||||
// Article is an event type that...
|
||||
Article = &K{30023}
|
||||
LongFormContent = &K{30023}
|
||||
DraftLongFormContent = &K{30024}
|
||||
EmojiSets = &K{30030}
|
||||
// ApplicationSpecificData is an event type stores data about application
|
||||
// configuration, this, like DMs and giftwraps must be protected by user
|
||||
// auth.
|
||||
ApplicationSpecificData = &K{30078}
|
||||
LiveEvent = &K{30311}
|
||||
UserStatuses = &K{30315}
|
||||
ClassifiedListing = &K{30402}
|
||||
DraftClassifiedListing = &K{30403}
|
||||
DateBasedCalendarEvent = &K{31922}
|
||||
TimeBasedCalendarEvent = &K{31923}
|
||||
Calendar = &K{31924}
|
||||
CalendarEventRSVP = &K{31925}
|
||||
HandlerRecommendation = &K{31989}
|
||||
HandlerInformation = &K{31990}
|
||||
// WaveLakeTrack which has no spec and uses malformed tags
|
||||
WaveLakeTrack = &K{32123}
|
||||
CommunityDefinition = &K{34550}
|
||||
ACLEvent = &K{39998}
|
||||
// ParameterizedReplaceableEnd is an event type that...
|
||||
ParameterizedReplaceableEnd = &K{39999}
|
||||
)
|
||||
|
||||
var MapMx sync.Mutex
|
||||
var Map = map[uint16]string{
|
||||
ProfileMetadata.K: "ProfileMetadata",
|
||||
TextNote.K: "TextNote",
|
||||
RecommendRelay.K: "RecommendRelay",
|
||||
FollowList.K: "FollowList",
|
||||
EncryptedDirectMessage.K: "EncryptedDirectMessage",
|
||||
EventDeletion.K: "EventDeletion",
|
||||
Repost.K: "Repost",
|
||||
Reaction.K: "Reaction",
|
||||
BadgeAward.K: "BadgeAward",
|
||||
ReadReceipt.K: "ReadReceipt",
|
||||
GenericRepost.K: "GenericRepost",
|
||||
ChannelCreation.K: "ChannelCreation",
|
||||
ChannelMetadata.K: "ChannelMetadata",
|
||||
ChannelMessage.K: "ChannelMessage",
|
||||
ChannelHideMessage.K: "ChannelHideMessage",
|
||||
ChannelMuteUser.K: "ChannelMuteUser",
|
||||
Bid.K: "Bid",
|
||||
BidConfirmation.K: "BidConfirmation",
|
||||
OpenTimestamps.K: "OpenTimestamps",
|
||||
FileMetadata.K: "FileMetadata",
|
||||
LiveChatMessage.K: "LiveChatMessage",
|
||||
ProblemTracker.K: "ProblemTracker",
|
||||
Reporting.K: "Reporting",
|
||||
Label.K: "Label",
|
||||
CommunityPostApproval.K: "CommunityPostApproval",
|
||||
JobRequestStart.K: "JobRequestStart",
|
||||
JobRequestEnd.K: "JobRequestEnd",
|
||||
JobResultStart.K: "JobResultStart",
|
||||
JobResultEnd.K: "JobResultEnd",
|
||||
JobFeedback.K: "JobFeedback",
|
||||
ZapGoal.K: "ZapGoal",
|
||||
ZapRequest.K: "ZapRequest",
|
||||
Zap.K: "Zap",
|
||||
Highlights.K: "Highlights",
|
||||
BlockList.K: "BlockList",
|
||||
PinList.K: "PinList",
|
||||
RelayListMetadata.K: "RelayListMetadata",
|
||||
BookmarkList.K: "BookmarkList",
|
||||
CommunitiesList.K: "CommunitiesList",
|
||||
PublicChatsList.K: "PublicChatsList",
|
||||
BlockedRelaysList.K: "BlockedRelaysList",
|
||||
SearchRelaysList.K: "SearchRelaysList",
|
||||
InterestsList.K: "InterestsList",
|
||||
UserEmojiList.K: "UserEmojiList",
|
||||
DMRelaysList.K: "DMRelaysList",
|
||||
FileStorageServerList.K: "FileStorageServerList",
|
||||
NWCWalletServiceInfo.K: "NWCWalletServiceInfo",
|
||||
LightningPubRPC.K: "LightningPubRPC",
|
||||
ClientAuthentication.K: "ClientAuthentication",
|
||||
WalletRequest.K: "WalletRequest",
|
||||
WalletResponse.K: "WalletResponse",
|
||||
WalletNotificationNip4.K: "WalletNotificationNip4",
|
||||
WalletNotification.K: "WalletNotification",
|
||||
NostrConnect.K: "NostrConnect",
|
||||
HTTPAuth.K: "HTTPAuth",
|
||||
FollowSets.K: "FollowSets",
|
||||
GenericLists.K: "GenericLists",
|
||||
RelaySets.K: "RelaySets",
|
||||
BookmarkSets.K: "BookmarkSets",
|
||||
CurationSets.K: "CurationSets",
|
||||
ProfileBadges.K: "ProfileBadges",
|
||||
BadgeDefinition.K: "BadgeDefinition",
|
||||
InterestSets.K: "InterestSets",
|
||||
StallDefinition.K: "StallDefinition",
|
||||
ProductDefinition.K: "ProductDefinition",
|
||||
MarketplaceUIUX.K: "MarketplaceUIUX",
|
||||
ProductSoldAsAuction.K: "ProductSoldAsAuction",
|
||||
LongFormContent.K: "LongFormContent",
|
||||
DraftLongFormContent.K: "DraftLongFormContent",
|
||||
EmojiSets.K: "EmojiSets",
|
||||
ApplicationSpecificData.K: "ApplicationSpecificData",
|
||||
ParameterizedReplaceableEnd.K: "ParameterizedReplaceableEnd",
|
||||
LiveEvent.K: "LiveEvent",
|
||||
UserStatuses.K: "UserStatuses",
|
||||
ClassifiedListing.K: "ClassifiedListing",
|
||||
DraftClassifiedListing.K: "DraftClassifiedListing",
|
||||
DateBasedCalendarEvent.K: "DateBasedCalendarEvent",
|
||||
TimeBasedCalendarEvent.K: "TimeBasedCalendarEvent",
|
||||
Calendar.K: "Calendar",
|
||||
CalendarEventRSVP.K: "CalendarEventRSVP",
|
||||
HandlerRecommendation.K: "HandlerRecommendation",
|
||||
HandlerInformation.K: "HandlerInformation",
|
||||
CommunityDefinition.K: "CommunityDefinition",
|
||||
}
|
||||
37
pkg/encoders/kind/kind_test.go
Normal file
37
pkg/encoders/kind/kind_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package kind
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"lol.mleku.dev/chk"
|
||||
|
||||
"lukechampine.com/frand"
|
||||
)
|
||||
|
||||
func TestMarshalUnmarshal(t *testing.T) {
|
||||
var err error
|
||||
k := make([]*K, 1000000)
|
||||
for i := range k {
|
||||
k[i] = New(uint16(frand.Intn(65535)))
|
||||
}
|
||||
mk := make([][]byte, len(k))
|
||||
for i := range mk {
|
||||
mk[i] = make([]byte, 0, 5) // 16 bits max 65535 = 5 characters
|
||||
}
|
||||
for i := range k {
|
||||
mk[i] = k[i].Marshal(mk[i])
|
||||
}
|
||||
k2 := make([]*K, len(k))
|
||||
for i := range k2 {
|
||||
k2[i] = New(0)
|
||||
}
|
||||
for i := range k2 {
|
||||
var r []byte
|
||||
if r, err = k2[i].Unmarshal(mk[i]); chk.E(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(r) != 0 {
|
||||
t.Fatalf("remainder after unmarshal: '%s'", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
150
pkg/encoders/kind/kinds.go
Normal file
150
pkg/encoders/kind/kinds.go
Normal file
@@ -0,0 +1,150 @@
|
||||
// Package kinds is a set of helpers for dealing with lists of kind numbers
|
||||
// including comparisons and encoding.
|
||||
package kind
|
||||
|
||||
import (
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/errorf"
|
||||
"next.orly.dev/pkg/encoders/ints"
|
||||
)
|
||||
|
||||
// S is an array of kind.K, used in filter.K and filter.S for searches.
|
||||
type S struct {
|
||||
K []*K
|
||||
}
|
||||
|
||||
// NewS creates a new kinds.S, if no parameter is given it just creates an empty zero kinds.S.
|
||||
func NewS(k ...*K) *S { return &S{k} }
|
||||
|
||||
// NewWithCap creates a new empty kinds.S with a given slice capacity.
|
||||
func NewWithCap(c int) *S { return &S{make([]*K, 0, c)} }
|
||||
|
||||
// FromIntSlice converts a []int into a kinds.S.
|
||||
func FromIntSlice(is []int) (k *S) {
|
||||
k = &S{}
|
||||
for i := range is {
|
||||
k.K = append(k.K, New(uint16(is[i])))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Len returns the number of elements in a kinds.S.
|
||||
func (k *S) Len() (l int) {
|
||||
if k == nil {
|
||||
return
|
||||
}
|
||||
return len(k.K)
|
||||
}
|
||||
|
||||
// Less returns which of two elements of a kinds.S is lower.
|
||||
func (k *S) Less(i, j int) bool { return k.K[i].K < k.K[j].K }
|
||||
|
||||
// Swap switches the position of two kinds.S elements.
|
||||
func (k *S) Swap(i, j int) {
|
||||
k.K[i].K, k.K[j].K = k.K[j].K, k.K[i].K
|
||||
}
|
||||
|
||||
// ToUint16 returns a []uint16 version of the kinds.S.
|
||||
func (k *S) ToUint16() (o []uint16) {
|
||||
for i := range k.K {
|
||||
o = append(o, k.K[i].ToU16())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Clone makes a new kind.K with the same members.
|
||||
func (k *S) Clone() (c *S) {
|
||||
c = &S{K: make([]*K, len(k.K))}
|
||||
for i := range k.K {
|
||||
c.K[i] = k.K[i]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Contains returns true if the provided element is found in the kinds.S.
|
||||
//
|
||||
// Note that the request must use the typed kind.K or convert the number thus.
|
||||
// Even if a custom number is found, this codebase does not have the logic to
|
||||
// deal with the kind so such a search is pointless and for which reason static
|
||||
// typing always wins. No mistakes possible with known quantities.
|
||||
func (k *S) Contains(s *K) bool {
|
||||
for i := range k.K {
|
||||
if k.K[i].Equal(s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Equals checks that the provided kind.K matches.
|
||||
func (k *S) Equals(t1 *S) bool {
|
||||
if len(k.K) != len(t1.K) {
|
||||
return false
|
||||
}
|
||||
for i := range k.K {
|
||||
if k.K[i] != t1.K[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Marshal renders the kinds.S into a JSON array of integers.
|
||||
func (k *S) Marshal(dst []byte) (b []byte) {
|
||||
b = dst
|
||||
b = append(b, '[')
|
||||
for i := range k.K {
|
||||
b = k.K[i].Marshal(b)
|
||||
if i != len(k.K)-1 {
|
||||
b = append(b, ',')
|
||||
}
|
||||
}
|
||||
b = append(b, ']')
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal decodes a provided JSON array of integers into a kinds.S.
|
||||
func (k *S) Unmarshal(b []byte) (r []byte, err error) {
|
||||
r = b
|
||||
var openedBracket bool
|
||||
for ; len(r) > 0; r = r[1:] {
|
||||
if !openedBracket && r[0] == '[' {
|
||||
openedBracket = true
|
||||
continue
|
||||
} else if openedBracket {
|
||||
if r[0] == ']' {
|
||||
// done
|
||||
return
|
||||
} else if r[0] == ',' {
|
||||
continue
|
||||
}
|
||||
kk := ints.New(0)
|
||||
if r, err = kk.Unmarshal(r); chk.E(err) {
|
||||
return
|
||||
}
|
||||
k.K = append(k.K, New(kk.Uint16()))
|
||||
if r[0] == ']' {
|
||||
r = r[1:]
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if !openedBracket {
|
||||
return nil, errorf.E(
|
||||
"kinds: failed to unmarshal\n%s\n%s\n%s", k,
|
||||
b, r,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// IsPrivileged returns true if any of the elements of a kinds.S are privileged (ie, they should
|
||||
// be privacy protected).
|
||||
func (k *S) IsPrivileged() (priv bool) {
|
||||
for i := range k.K {
|
||||
if k.K[i].IsPrivileged() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
36
pkg/encoders/kind/kinds_test.go
Normal file
36
pkg/encoders/kind/kinds_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package kind
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"lol.mleku.dev/chk"
|
||||
"lukechampine.com/frand"
|
||||
)
|
||||
|
||||
func TestUnmarshalKindsArray(t *testing.T) {
|
||||
k := &S{make([]*K, 100)}
|
||||
for i := range k.K {
|
||||
k.K[i] = New(uint16(frand.Intn(65535)))
|
||||
}
|
||||
var dst []byte
|
||||
var err error
|
||||
if dst = k.Marshal(dst); chk.E(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
k2 := &S{}
|
||||
var rem []byte
|
||||
if rem, err = k2.Unmarshal(dst); chk.E(err) {
|
||||
return
|
||||
}
|
||||
if len(rem) > 0 {
|
||||
t.Fatalf("failed to unmarshal, remnant afterwards '%s'", rem)
|
||||
}
|
||||
for i := range k.K {
|
||||
if *k.K[i] != *k2.K[i] {
|
||||
t.Fatalf(
|
||||
"failed to unmarshal at element %d; got %x, expected %x",
|
||||
i, k.K[i], k2.K[i],
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
119
pkg/encoders/timestamp/timestamp.go
Normal file
119
pkg/encoders/timestamp/timestamp.go
Normal file
@@ -0,0 +1,119 @@
|
||||
// Package timestamp is a set of helpers for working with timestamps including
|
||||
// encoding and conversion to various integer forms, from time.Time and varints.
|
||||
package timestamp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/errorf"
|
||||
"next.orly.dev/pkg/encoders/ints"
|
||||
)
|
||||
|
||||
// T is a convenience type for UNIX 64 bit timestamps of 1 second
|
||||
// precision.
|
||||
type T struct{ V int64 }
|
||||
|
||||
// New creates a new timestamp.T, as zero or optionally from the first variadic
|
||||
// parameter as int64.
|
||||
func New(x ...int64) (t *T) {
|
||||
t = &T{}
|
||||
if len(x) > 0 {
|
||||
t.V = x[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Now returns the current UNIX timestamp of the current second.
|
||||
func Now() *T {
|
||||
tt := T{time.Now().Unix()}
|
||||
return &tt
|
||||
}
|
||||
|
||||
// U64 returns the current UNIX timestamp of the current second as uint64.
|
||||
func (t *T) U64() uint64 {
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
return uint64(t.V)
|
||||
}
|
||||
|
||||
// I64 returns the current UNIX timestamp of the current second as int64.
|
||||
func (t *T) I64() int64 {
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
return t.V
|
||||
}
|
||||
|
||||
// Time converts a timestamp.Time value into a canonical UNIX 64 bit 1 second
|
||||
// precision timestamp.
|
||||
func (t *T) Time() time.Time { return time.Unix(t.V, 0) }
|
||||
|
||||
// Int returns the timestamp as an int.
|
||||
func (t *T) Int() int {
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
return int(t.V)
|
||||
}
|
||||
|
||||
// Bytes returns a timestamp as an 8 byte thing.
|
||||
func (t *T) Bytes() (b []byte) {
|
||||
b = make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(b, uint64(t.V))
|
||||
return
|
||||
}
|
||||
|
||||
// FromTime returns a T from a time.Time
|
||||
func FromTime(t time.Time) *T { return &T{t.Unix()} }
|
||||
|
||||
// FromUnix converts from a standard int64 unix timestamp.
|
||||
func FromUnix(t int64) *T { return &T{t} }
|
||||
|
||||
func (t *T) FromInt(i int) { *t = T{int64(i)} }
|
||||
|
||||
// FromBytes converts from a string of raw bytes.
|
||||
func FromBytes(b []byte) *T { return &T{int64(binary.BigEndian.Uint64(b))} }
|
||||
|
||||
// FromVarint decodes a varint and returns the remainder of the bytes and the encoded
|
||||
// timestamp.T.
|
||||
func FromVarint(b []byte) (t *T, rem []byte, err error) {
|
||||
n, read := binary.Varint(b)
|
||||
if read < 1 {
|
||||
err = errorf.E("failed to decode varint timestamp %v", b)
|
||||
return
|
||||
}
|
||||
t = &T{n}
|
||||
rem = b[:read]
|
||||
return
|
||||
}
|
||||
|
||||
// String renders a timestamp.T as a string.
|
||||
func (t *T) String() (s string) {
|
||||
b := make([]byte, 0, 20)
|
||||
tt := ints.New(t.U64())
|
||||
b = tt.Marshal(b)
|
||||
return unsafe.String(&b[0], len(b))
|
||||
}
|
||||
|
||||
// Marshal a timestamp.T into bytes and append to a provided byte slice.
|
||||
func (t *T) Marshal(dst []byte) (b []byte) { return ints.New(t.U64()).Marshal(dst) }
|
||||
|
||||
// Unmarshal a byte slice with an encoded timestamp.T value and append it to a provided byte
|
||||
// slice.
|
||||
func (t *T) Unmarshal(b []byte) (r []byte, err error) {
|
||||
n := ints.New(0)
|
||||
if r, err = n.Unmarshal(b); chk.E(err) {
|
||||
return
|
||||
}
|
||||
*t = T{n.Int64()}
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalJSON marshals a timestamp.T using the json MarshalJSON interface.
|
||||
func (t *T) MarshalJSON() ([]byte, error) {
|
||||
return ints.New(t.U64()).Marshal(nil), nil
|
||||
}
|
||||
Reference in New Issue
Block a user