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 {loginAddress as githubAddress} from "../github/nodes";
|
||||
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.
|
||||
*
|
||||
|
@ -49,6 +53,13 @@ export function resolveAlias(
|
|||
}
|
||||
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:
|
||||
throw new Error(`Unknown type for alias: ${alias}`);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import {resolveAlias} from "./alias";
|
||||
import {loginAddress as githubAddress} from "../github/nodes";
|
||||
import {userAddress as discourseAddress} from "../discourse/address";
|
||||
import {identityAddress} from "./identity";
|
||||
|
||||
describe("src/plugins/identity/alias", () => {
|
||||
describe("resolveAlias", () => {
|
||||
|
@ -77,6 +78,16 @@ describe("src/plugins/identity/alias", () => {
|
|||
const b = resolveAlias("discourse/@login", url);
|
||||
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 {nodePrefix} from "./declaration";
|
||||
import {type Alias} from "./alias";
|
||||
import type {NodeAddressT} from "../../core/graph";
|
||||
|
||||
/**
|
||||
* A Username is a locally (within-instance) unique identifier for a user of
|
||||
|
@ -30,17 +31,31 @@ export type IdentitySpec = {|
|
|||
+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.
|
||||
*/
|
||||
export function identityNode(identity: Identity): Node {
|
||||
const re = new RegExp(USERNAME_PATTERN);
|
||||
const match = re.exec(identity.username);
|
||||
if (match == null) {
|
||||
throw new Error(`Invalid username: ${identity.username}`);
|
||||
}
|
||||
const username = match[1];
|
||||
const address = NodeAddress.append(nodePrefix, username);
|
||||
const username = validateUsername(identity.username);
|
||||
const address = identityAddress(username);
|
||||
const description = `@${username}`;
|
||||
return {address, timestampMs: null, description};
|
||||
}
|
||||
|
||||
export function identityAddress(username: Username): NodeAddressT {
|
||||
return NodeAddress.append(nodePrefix, validateUsername(username));
|
||||
}
|
||||
|
|
|
@ -1,26 +1,51 @@
|
|||
// @flow
|
||||
|
||||
import {NodeAddress} from "../../core/graph";
|
||||
import {identityNode} from "./identity";
|
||||
import {identityAddress, identityNode, validateUsername} from "./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", () => {
|
||||
const identity = {username: "foo", aliases: ["github/foo"]};
|
||||
const n = identityNode(identity);
|
||||
expect(n.address).toEqual(
|
||||
NodeAddress.fromParts(["sourcecred", "identity", "foo"])
|
||||
);
|
||||
expect(identityAddress(identity.username)).toEqual(n.address);
|
||||
expect(n.timestampMs).toEqual(null);
|
||||
expect(n.description).toEqual("@foo");
|
||||
});
|
||||
it("errors for an empty username", () => {
|
||||
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", () => {
|
||||
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", () => {
|
||||
const identity = {username: "@foo", aliases: ["github/foo"]};
|
||||
|
@ -28,6 +53,7 @@ describe("src/plugins/identity/identity", () => {
|
|||
expect(n.address).toEqual(
|
||||
NodeAddress.fromParts(["sourcecred", "identity", "foo"])
|
||||
);
|
||||
expect(identityAddress(identity.username)).toEqual(n.address);
|
||||
expect(n.timestampMs).toEqual(null);
|
||||
expect(n.description).toEqual("@foo");
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue