Skip to content

Commit 341115c

Browse files
committed
[DAPS-1513] Design Pattern Translation: Convert Repository Factory to Rust-Compatible js
1 parent 569de98 commit 341115c

File tree

4 files changed

+682
-0
lines changed

4 files changed

+682
-0
lines changed
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
"use strict";
2+
3+
/**
4+
* Example usage of the new repository type system
5+
* Demonstrates Rust-compatible patterns in JavaScript
6+
*/
7+
8+
const { RepositoryType, Result } = require("./types");
9+
const { createRepositoryByType } = require("./factory");
10+
const { RepositoryOps } = require("./operations");
11+
12+
/**
13+
* Example 1: Creating repositories using factory pattern
14+
* Factory pattern is common in Rust for complex object construction
15+
* @returns {Promise<{globus: *, metadata: *}>} object containing created repositories
16+
* @see https://doc.rust-lang.org/book/ch17-03-oo-design-patterns.html
17+
*/
18+
async function createRepositoryExample() {
19+
// Create a Globus repository
20+
const globusConfig = {
21+
id: "science_data_repo",
22+
type: RepositoryType.GLOBUS,
23+
title: "Science Data Repository",
24+
desc: "Repository for scientific datasets",
25+
capacity: 10000000000, // 10GB
26+
admins: ["u/scientist1", "u/scientist2"],
27+
pub_key: "123ABC...",
28+
address: "data.science.org",
29+
endpoint: "endpoint-abc123",
30+
path: "/mnt/storage/repos/science_data_repo",
31+
domain: "science.org",
32+
};
33+
34+
const globusResult = createRepositoryByType(globusConfig);
35+
if (!globusResult.ok) {
36+
console.error("Failed to create Globus repo:", globusResult.error);
37+
return;
38+
}
39+
40+
// Create a metadata-only repository
41+
const metadataConfig = {
42+
id: "metadata_catalog",
43+
type: RepositoryType.METADATA_ONLY,
44+
title: "Metadata Catalog",
45+
desc: "Repository for metadata records only",
46+
capacity: 1000000, // Logical limit for records
47+
admins: ["u/cataloger1"],
48+
};
49+
50+
const metadataResult = createRepositoryByType(metadataConfig);
51+
if (!metadataResult.ok) {
52+
console.error("Failed to create metadata repo:", metadataResult.error);
53+
return;
54+
}
55+
56+
return { globus: globusResult.value, metadata: metadataResult.value };
57+
}
58+
59+
/**
60+
* Example 2: Using trait-like operations
61+
* Using traits allows polymorphic behavior without knowing concrete types
62+
* @returns {Promise<*>} Allocation result
63+
* @see https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
64+
*/
65+
async function useRepositoryOperations() {
66+
// Find a repository
67+
const findResult = RepositoryOps.find("repo/science_data_repo");
68+
if (!findResult.ok) {
69+
console.error("Repository not found:", findResult.error);
70+
return;
71+
}
72+
73+
const repository = findResult.value;
74+
75+
// Check if it supports data operations
76+
const supportsDataResult = RepositoryOps.supportsDataOperations(repository);
77+
if (supportsDataResult.ok && supportsDataResult.value) {
78+
console.log("Repository supports data operations");
79+
}
80+
81+
// Create an allocation
82+
const allocResult = RepositoryOps.createAllocation(repository, {
83+
subject: "d/dataset_001",
84+
size: 1000000000, // 1GB
85+
path: "/datasets/2024/dataset_001",
86+
metadata: {
87+
project: "Climate Research",
88+
created_by: "Dr. Smith",
89+
},
90+
});
91+
92+
if (!allocResult.ok) {
93+
console.error("Allocation failed:", allocResult.error);
94+
return;
95+
}
96+
97+
// Handle result based on execution method
98+
const allocation = allocResult.value;
99+
if (allocation.execution_method === "task") {
100+
console.log("Allocation queued as task:", allocation.task.task_id);
101+
// Would monitor task progress...
102+
} else {
103+
console.log("Allocation completed directly:", allocation.result);
104+
}
105+
106+
return allocation;
107+
}
108+
109+
/**
110+
* Example 3: Pattern matching on repository types
111+
* Pattern matching is fundamental in Rust for handling enum variants
112+
* @param {object} repository - Repository object with type and data fields
113+
* @returns {{ok: boolean, error?: *, value?: *}} Result of handling repository
114+
* @see https://doc.rust-lang.org/book/ch06-02-match.html
115+
*/
116+
function handleRepositoryByType(repository) {
117+
// Similar to Rust match expression
118+
switch (repository.type) {
119+
case RepositoryType.GLOBUS:
120+
return handleGlobusRepository(repository.data);
121+
122+
case RepositoryType.METADATA_ONLY:
123+
return handleMetadataRepository(repository.data);
124+
125+
default:
126+
return Result.err({
127+
code: 400,
128+
message: `Unknown repository type: ${repository.type}`,
129+
});
130+
}
131+
}
132+
133+
function handleGlobusRepository(repoData) {
134+
console.log(`Globus repository at ${repoData.endpoint}`);
135+
// Globus-specific logic...
136+
return Result.ok({
137+
type: "globus",
138+
endpoint: repoData.endpoint,
139+
path: repoData.path,
140+
});
141+
}
142+
143+
function handleMetadataRepository(repoData) {
144+
console.log(`Metadata-only repository: ${repoData.title}`);
145+
// Metadata-specific logic...
146+
return Result.ok({
147+
type: "metadata",
148+
record_limit: repoData.capacity,
149+
});
150+
}
151+
152+
/**
153+
* Example 4: Error handling with Result pattern
154+
* Early returns emulate Rust's ? operator for error propagation
155+
* @returns {Promise<{ok: boolean, value?: *, error?: *}>} Result of operations
156+
* @see https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator
157+
*/
158+
async function robustRepositoryOperation() {
159+
// Chain operations with early return on error
160+
const findResult = RepositoryOps.find("repo/test_repo");
161+
if (!findResult.ok) {
162+
return findResult; // Propagate error - like ? in Rust
163+
}
164+
165+
const repository = findResult.value;
166+
167+
const validateResult = RepositoryOps.validate(repository);
168+
if (!validateResult.ok) {
169+
return validateResult; // Propagate error
170+
}
171+
172+
const capacityResult = RepositoryOps.getCapacityInfo(repository);
173+
if (!capacityResult.ok) {
174+
return capacityResult; // Propagate error
175+
}
176+
177+
// All operations succeeded
178+
return Result.ok({
179+
repository: repository,
180+
capacity: capacityResult.value,
181+
});
182+
}
183+
184+
/**
185+
* Example 5: Composition over inheritance
186+
* Rust doesn't have inheritance - prefer composition of behaviors
187+
* @type {object}
188+
* @property {function(object, string): void} logAccess - Log repository access
189+
* @property {function(object, number): {ok: boolean, error?: *, value?: *}} checkQuota - Check repository quota
190+
* @property {function(object, object): Promise<{ok: boolean, error?: *, value?: *}>} allocateWithQuotaCheck - Allocate with quota check
191+
* @see https://doc.rust-lang.org/book/ch17-03-oo-design-patterns.html
192+
*/
193+
const RepositoryBehaviors = {
194+
// Shared behaviors as standalone functions
195+
logAccess: (repository, userId) => {
196+
console.log(`User ${userId} accessed repository ${repository.data._id}`);
197+
},
198+
199+
checkQuota: (repository, requestedSize) => {
200+
const capacityResult = RepositoryOps.getCapacityInfo(repository);
201+
if (!capacityResult.ok) return capacityResult;
202+
203+
const capacity = capacityResult.value;
204+
if (capacity.available_capacity < requestedSize) {
205+
return Result.err({
206+
code: 507,
207+
message: "Insufficient storage capacity",
208+
});
209+
}
210+
return Result.ok(true);
211+
},
212+
213+
// Type-specific behaviors composed from shared ones
214+
allocateWithQuotaCheck: async (repository, params) => {
215+
// Compose behaviors
216+
const quotaResult = RepositoryBehaviors.checkQuota(repository, params.size);
217+
if (!quotaResult.ok) return quotaResult;
218+
219+
RepositoryBehaviors.logAccess(repository, params.requested_by);
220+
221+
return RepositoryOps.createAllocation(repository, params);
222+
},
223+
};
224+
225+
module.exports = {
226+
createRepositoryExample,
227+
useRepositoryOperations,
228+
handleRepositoryByType,
229+
robustRepositoryOperation,
230+
RepositoryBehaviors,
231+
};
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
"use strict";
2+
3+
const {
4+
RepositoryType,
5+
Result,
6+
createRepository,
7+
createRepositoryData,
8+
createGlobusConfig,
9+
} = require("./types");
10+
const { validateGlobusConfig, validateMetadataConfig } = require("./validation");
11+
const globusRepo = require("./globus");
12+
const metadataRepo = require("./metadata");
13+
const g_lib = require("../support");
14+
15+
/**
16+
* Repository factory using Rust-compatible patterns
17+
* Uses switch/case for type-based polymorphism instead of inheritance
18+
*/
19+
20+
/**
21+
* Create repository based on type (similar to Rust match expression)
22+
* Rust's match expression provides exhaustive pattern matching
23+
* JavaScript's switch is used here to emulate this pattern
24+
* @param {object} config - Repository configuration object
25+
* @param {string} config.id - Repository ID
26+
* @param {string} config.type - Repository type (from RepositoryType enum)
27+
* @param {string} config.title - Repository title
28+
* @param {string} [config.desc] - Repository description
29+
* @param {number} config.capacity - Storage capacity in bytes
30+
* @param {string[]} config.admins - Array of admin user IDs
31+
* @param {string} [config.endpoint] - Globus endpoint (required for GLOBUS type)
32+
* @param {string} [config.path] - File path (required for GLOBUS type)
33+
* @param {string} [config.pub_key] - Public SSH key (required for GLOBUS type)
34+
* @param {string} [config.address] - Network address (required for GLOBUS type)
35+
* @param {string} [config.exp_path] - Export path (optional for GLOBUS type)
36+
* @param {string} [config.domain] - Domain name (required for GLOBUS type)
37+
* @returns {{ok: boolean, error: *}|{ok: boolean, value: *}} Result object containing repository or error
38+
* @see https://doc.rust-lang.org/book/ch06-02-match.html
39+
*/
40+
const createRepositoryByType = (config) => {
41+
// Validate common fields
42+
if (!config.id || !config.type || !config.title || !config.capacity || !config.admins) {
43+
return Result.err({
44+
code: g_lib.ERR_INVALID_PARAM,
45+
message: "Missing required repository fields",
46+
});
47+
}
48+
49+
/**
50+
* Type-based creation using switch (Rust match pattern)
51+
* Each case is like a match arm in Rust, handling a specific variant
52+
* @see https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html
53+
*/
54+
switch (config.type) {
55+
case RepositoryType.GLOBUS: {
56+
const validationResult = validateGlobusConfig(config);
57+
if (!validationResult.ok) {
58+
return validationResult;
59+
}
60+
61+
const globusConfig = createGlobusConfig({
62+
endpoint: config.endpoint,
63+
path: config.path,
64+
pub_key: config.pub_key,
65+
address: config.address,
66+
exp_path: config.exp_path,
67+
domain: config.domain,
68+
});
69+
70+
const repoData = createRepositoryData({
71+
id: config.id,
72+
type: config.type,
73+
title: config.title,
74+
desc: config.desc,
75+
capacity: config.capacity,
76+
admins: config.admins,
77+
typeSpecific: globusConfig,
78+
});
79+
80+
return Result.ok(createRepository(RepositoryType.GLOBUS, repoData));
81+
}
82+
83+
case RepositoryType.METADATA_ONLY: {
84+
const validationResult = validateMetadataConfig(config);
85+
if (!validationResult.ok) {
86+
return validationResult;
87+
}
88+
89+
const repoData = createRepositoryData({
90+
id: config.id,
91+
type: config.type,
92+
title: config.title,
93+
desc: config.desc,
94+
capacity: config.capacity,
95+
admins: config.admins,
96+
});
97+
98+
return Result.ok(createRepository(RepositoryType.METADATA_ONLY, repoData));
99+
}
100+
101+
default:
102+
/**
103+
* In Rust, match must be exhaustive - all cases must be handled
104+
* The default case ensures we handle unknown variants
105+
* @see https://doc.rust-lang.org/book/ch06-02-match.html#matching-with-option-t
106+
*/
107+
return Result.err({
108+
code: g_lib.ERR_INVALID_PARAM,
109+
message: `Unknown repository type: ${config.type}`,
110+
});
111+
}
112+
};
113+
114+
/**
115+
* Get repository implementation based on type
116+
* This emulates Rust's trait object dynamic dispatch
117+
* @param {string} repositoryType - Repository type from RepositoryType enum
118+
* @returns {object|null} Repository implementation object or null if not found
119+
* @see https://doc.rust-lang.org/book/ch17-02-trait-objects.html
120+
*/
121+
const getRepositoryImplementation = (repositoryType) => {
122+
switch (repositoryType) {
123+
case RepositoryType.GLOBUS:
124+
return globusRepo;
125+
case RepositoryType.METADATA_ONLY:
126+
return metadataRepo;
127+
default:
128+
return null;
129+
}
130+
};
131+
132+
/**
133+
* Execute operation on repository using dynamic dispatch
134+
* This pattern emulates Rust's trait method dispatch
135+
* @param {object} repository - Repository object with type and data fields
136+
* @param {string} operation - Operation name to execute
137+
* @param {...*} args - Additional arguments to pass to the operation
138+
* @returns {{ok: boolean, error: *}|*} Result of the operation
139+
* @see https://doc.rust-lang.org/book/ch17-02-trait-objects.html#trait-objects-perform-dynamic-dispatch
140+
*/
141+
const executeRepositoryOperation = (repository, operation, ...args) => {
142+
const impl = getRepositoryImplementation(repository.type);
143+
if (!impl) {
144+
return Result.err({
145+
code: g_lib.ERR_INVALID_PARAM,
146+
message: `No implementation for repository type: ${repository.type}`,
147+
});
148+
}
149+
150+
if (typeof impl[operation] !== "function") {
151+
return Result.err({
152+
code: g_lib.ERR_NOT_IMPLEMENTED,
153+
message: `Operation '${operation}' not implemented for type: ${repository.type}`,
154+
});
155+
}
156+
157+
return impl[operation](repository.data, ...args);
158+
};
159+
160+
module.exports = {
161+
createRepositoryByType,
162+
getRepositoryImplementation,
163+
executeRepositoryOperation,
164+
};

0 commit comments

Comments
 (0)