mirror of
https://github.com/status-im/sourcecred.git
synced 2025-02-27 11:40:26 +00:00
resolveAlias: add support for sourcecred identity (#1752)
This commit modifies the resolveAlias function in the identity plugin's alias module so that it now allows you to convert sourcecred identity aliases (e.g. "sourcecred/user") back into NodeAddresses. This will be necessary so that we can convert our ad-hoc distributions and transfers (which use aliases, including SourceCred identity aliases) into the more robust formats for the productionized grain ledger, which use full node addresses. I did a slight refactor on the identity module to expose a method for constructing the address without the rest of the node. Test plan: `yarn test`
This commit is contained in:
parent
830c83bedf
commit
203b48066c
@ -4,6 +4,10 @@ import {type NodeAddressT} from "../../core/graph";
|
|||||||
import {githubOwnerPattern} from "../github/repoId";
|
import {githubOwnerPattern} from "../github/repoId";
|
||||||
import {loginAddress as githubAddress} from "../github/nodes";
|
import {loginAddress as githubAddress} from "../github/nodes";
|
||||||
import {userAddress as discourseAddress} from "../discourse/address";
|
import {userAddress as discourseAddress} from "../discourse/address";
|
||||||
|
import {
|
||||||
|
identityAddress,
|
||||||
|
USERNAME_PATTERN as _VALID_IDENTITY_PATTERN,
|
||||||
|
} from "./identity";
|
||||||
|
|
||||||
/** An Alias is a string specification of an identity within another plugin.
|
/** An Alias is a string specification of an identity within another plugin.
|
||||||
*
|
*
|
||||||
@ -49,6 +53,13 @@ export function resolveAlias(
|
|||||||
}
|
}
|
||||||
return discourseAddress(discourseUrl, match[1]);
|
return discourseAddress(discourseUrl, match[1]);
|
||||||
}
|
}
|
||||||
|
case "sourcecred": {
|
||||||
|
const match = name.match(_VALID_IDENTITY_PATTERN);
|
||||||
|
if (!match) {
|
||||||
|
throw new Error(`Invalid SourceCred identity: ${name}`);
|
||||||
|
}
|
||||||
|
return identityAddress(match[1]);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown type for alias: ${alias}`);
|
throw new Error(`Unknown type for alias: ${alias}`);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import {resolveAlias} from "./alias";
|
import {resolveAlias} from "./alias";
|
||||||
import {loginAddress as githubAddress} from "../github/nodes";
|
import {loginAddress as githubAddress} from "../github/nodes";
|
||||||
import {userAddress as discourseAddress} from "../discourse/address";
|
import {userAddress as discourseAddress} from "../discourse/address";
|
||||||
|
import {identityAddress} from "./identity";
|
||||||
|
|
||||||
describe("src/plugins/identity/alias", () => {
|
describe("src/plugins/identity/alias", () => {
|
||||||
describe("resolveAlias", () => {
|
describe("resolveAlias", () => {
|
||||||
@ -77,6 +78,16 @@ describe("src/plugins/identity/alias", () => {
|
|||||||
const b = resolveAlias("discourse/@login", url);
|
const b = resolveAlias("discourse/@login", url);
|
||||||
expect(a).toEqual(b);
|
expect(a).toEqual(b);
|
||||||
});
|
});
|
||||||
|
it("a sourcecred identity", () => {
|
||||||
|
const actual = resolveAlias("sourcecred/example", null);
|
||||||
|
const expected = identityAddress("example");
|
||||||
|
expect(actual).toEqual(expected);
|
||||||
|
});
|
||||||
|
it("a sourcecred identity with prefixed @", () => {
|
||||||
|
const a = resolveAlias("sourcecred/example", null);
|
||||||
|
const b = resolveAlias("sourcecred/@example", null);
|
||||||
|
expect(a).toEqual(b);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import {NodeAddress, type Node} from "../../core/graph";
|
import {NodeAddress, type Node} from "../../core/graph";
|
||||||
import {nodePrefix} from "./declaration";
|
import {nodePrefix} from "./declaration";
|
||||||
import {type Alias} from "./alias";
|
import {type Alias} from "./alias";
|
||||||
|
import type {NodeAddressT} from "../../core/graph";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Username is a locally (within-instance) unique identifier for a user of
|
* A Username is a locally (within-instance) unique identifier for a user of
|
||||||
@ -30,17 +31,31 @@ export type IdentitySpec = {|
|
|||||||
+discourseServerUrl: string | null,
|
+discourseServerUrl: string | null,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal method for validating a username.
|
||||||
|
*
|
||||||
|
* Returns the username with any leading @ symbol stripped.
|
||||||
|
* Throws an error if the username is invalid.
|
||||||
|
*/
|
||||||
|
export function validateUsername(username: string): Username {
|
||||||
|
const re = new RegExp(USERNAME_PATTERN);
|
||||||
|
const match = re.exec(username);
|
||||||
|
if (match == null) {
|
||||||
|
throw new Error(`invalid username: ${username}`);
|
||||||
|
}
|
||||||
|
return match[1];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new node representing an identity.
|
* Create a new node representing an identity.
|
||||||
*/
|
*/
|
||||||
export function identityNode(identity: Identity): Node {
|
export function identityNode(identity: Identity): Node {
|
||||||
const re = new RegExp(USERNAME_PATTERN);
|
const username = validateUsername(identity.username);
|
||||||
const match = re.exec(identity.username);
|
const address = identityAddress(username);
|
||||||
if (match == null) {
|
|
||||||
throw new Error(`Invalid username: ${identity.username}`);
|
|
||||||
}
|
|
||||||
const username = match[1];
|
|
||||||
const address = NodeAddress.append(nodePrefix, username);
|
|
||||||
const description = `@${username}`;
|
const description = `@${username}`;
|
||||||
return {address, timestampMs: null, description};
|
return {address, timestampMs: null, description};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function identityAddress(username: Username): NodeAddressT {
|
||||||
|
return NodeAddress.append(nodePrefix, validateUsername(username));
|
||||||
|
}
|
||||||
|
@ -1,26 +1,51 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import {NodeAddress} from "../../core/graph";
|
import {NodeAddress} from "../../core/graph";
|
||||||
import {identityNode} from "./identity";
|
import {identityAddress, identityNode, validateUsername} from "./identity";
|
||||||
|
|
||||||
describe("src/plugins/identity/identity", () => {
|
describe("src/plugins/identity/identity", () => {
|
||||||
describe("identityNode", () => {
|
describe("validateUsername", () => {
|
||||||
|
it("accepts good inputs", () => {
|
||||||
|
const good = ["foo", "123", "foo-123", "foo_bar-123", "_S-A-M_"];
|
||||||
|
const usernames = good.map(validateUsername);
|
||||||
|
expect(good).toEqual(usernames);
|
||||||
|
});
|
||||||
|
it("strips leading @-signs", () => {
|
||||||
|
const good = ["foo", "123", "foo-123", "foo_bar-123", "_S-A-M_"];
|
||||||
|
const usernames = good.map((g) => validateUsername("@" + g));
|
||||||
|
expect(good).toEqual(usernames);
|
||||||
|
});
|
||||||
|
it("rejects bad inputs", () => {
|
||||||
|
const bad = ["", "@", "@foo@", "foo$bar"];
|
||||||
|
for (const b of bad) {
|
||||||
|
expect(() => validateUsername(b)).toThrow("invalid username");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("identityNode & identityAddress", () => {
|
||||||
it("works as expected for valid identity", () => {
|
it("works as expected for valid identity", () => {
|
||||||
const identity = {username: "foo", aliases: ["github/foo"]};
|
const identity = {username: "foo", aliases: ["github/foo"]};
|
||||||
const n = identityNode(identity);
|
const n = identityNode(identity);
|
||||||
expect(n.address).toEqual(
|
expect(n.address).toEqual(
|
||||||
NodeAddress.fromParts(["sourcecred", "identity", "foo"])
|
NodeAddress.fromParts(["sourcecred", "identity", "foo"])
|
||||||
);
|
);
|
||||||
|
expect(identityAddress(identity.username)).toEqual(n.address);
|
||||||
expect(n.timestampMs).toEqual(null);
|
expect(n.timestampMs).toEqual(null);
|
||||||
expect(n.description).toEqual("@foo");
|
expect(n.description).toEqual("@foo");
|
||||||
});
|
});
|
||||||
it("errors for an empty username", () => {
|
it("errors for an empty username", () => {
|
||||||
const identity = {username: "", aliases: ["github/foo"]};
|
const identity = {username: "", aliases: ["github/foo"]};
|
||||||
expect(() => identityNode(identity)).toThrowError("Invalid username");
|
expect(() => identityNode(identity)).toThrowError("invalid username");
|
||||||
|
expect(() => identityAddress(identity.username)).toThrowError(
|
||||||
|
"invalid username"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
it("errors for a bad username", () => {
|
it("errors for a bad username", () => {
|
||||||
const identity = {username: "$foo$bar", aliases: ["github/foo"]};
|
const identity = {username: "$foo$bar", aliases: ["github/foo"]};
|
||||||
expect(() => identityNode(identity)).toThrowError("Invalid username");
|
expect(() => identityNode(identity)).toThrowError("invalid username");
|
||||||
|
expect(() => identityAddress(identity.username)).toThrowError(
|
||||||
|
"invalid username"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
it("strips redundant leading @ from the description and address", () => {
|
it("strips redundant leading @ from the description and address", () => {
|
||||||
const identity = {username: "@foo", aliases: ["github/foo"]};
|
const identity = {username: "@foo", aliases: ["github/foo"]};
|
||||||
@ -28,6 +53,7 @@ describe("src/plugins/identity/identity", () => {
|
|||||||
expect(n.address).toEqual(
|
expect(n.address).toEqual(
|
||||||
NodeAddress.fromParts(["sourcecred", "identity", "foo"])
|
NodeAddress.fromParts(["sourcecred", "identity", "foo"])
|
||||||
);
|
);
|
||||||
|
expect(identityAddress(identity.username)).toEqual(n.address);
|
||||||
expect(n.timestampMs).toEqual(null);
|
expect(n.timestampMs).toEqual(null);
|
||||||
expect(n.description).toEqual("@foo");
|
expect(n.description).toEqual("@foo");
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user