Skip to content

Commit

Permalink
chore: simplify url-shortener example (#3594)
Browse files Browse the repository at this point in the history
The URL shortener example as its written has a race condition because it's trying to ensure that every URL gets a unique shortened ID. If we relax this constraint (which is reasonable in practice), then the race condition is avoided and we can simplify the implementation.

Closes #2823

## Checklist

- [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted)
- [x] Description explains motivation and solution
- [x] Tests added (always)
- [ ] Docs updated (only required for features)
- [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing

*By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*.
  • Loading branch information
Chriscbr authored Jul 25, 2023
1 parent 1fa0224 commit 2b5df31
Showing 1 changed file with 11 additions and 47 deletions.
58 changes: 11 additions & 47 deletions examples/proposed/url-shortener.w
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,33 @@ bring cloud;
bring math;
bring http;

let ALPHANUMERIC_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

class UrlShortener {
urlLookup: cloud.Bucket;
idLookup: cloud.Bucket;
lookup: cloud.Bucket;

init() {
// map from urls to ids
this.urlLookup = new cloud.Bucket() as "UrlLookup";

// map from ids to urls
this.idLookup = new cloud.Bucket() as "IdLookup";
this.lookup = new cloud.Bucket() as "IdLookup";
}

// Returns a short id for the given url. Creates a new id if one does not
// already exist.
//
// In the current implementation, when a new URL is shortened, two
// transactions are performed:
//
// 1. The url is added to the urlLookup bucket.
// 2. The id is added to the idLookup bucket.
//
// If the second transaction fails, the first transaction is not rolled
// back. This means that the urlLookup bucket may contain urls that do
// not have a corresponding id in the idLookup bucket. This is not a
// problem, since `getId` will only return a shortened ID once both
// transactions have completed successfully.
// Generates a short id for the given url.
inflight getId(url: str): str {
let id = this.urlLookup.tryGet(url);
if let id = id {
// ensure that the id exists in idLookup
if !this.idLookup.exists(id) {
this.idLookup.put(id, url);
}
return id;
}

let newId = this._makeId();

// (transaction 1)
this.urlLookup.put(url, newId);

// (transaction 2)
this.idLookup.put(newId, url);

return newId;
let id = this._makeId();
this.lookup.put(id, url);
return id;
}

// Get the url for the given id. Returns nil if the url does not have a
// corresponding id.
// Get the url for the given id. Returns nil if the id is invalid.
inflight getUrl(id: str): str? {
return this.idLookup.tryGet(id);
return this.lookup.tryGet(id);
}

inflight _makeId(): str {
let ALPHANUMERIC_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let var id = "";

for i in 0..5 {
let randomIndex = math.floor(math.random() * ALPHANUMERIC_CHARS.length);
id = id + ALPHANUMERIC_CHARS.at(randomIndex);
}

return id;
}
}
Expand Down Expand Up @@ -144,12 +108,12 @@ test "shorten url" {
assert(newUrl.startsWith(urlShortenerApi.api.url));
}

test "shorten url twice" {
test "shorten same url twice" {
let response1 = http.post("${urlShortenerApi.api.url}/create", body: Json.stringify({ url: TEST_URL }));
let newUrl1 = Json.tryParse(response1.body ?? "")?.get("shortenedUrl")?.asStr();
let response2 = http.post("${urlShortenerApi.api.url}/create", body: Json.stringify({ url: TEST_URL }));
let newUrl2 = Json.tryParse(response2.body ?? "")?.get("shortenedUrl")?.asStr();
assert(newUrl1 == newUrl2);
assert(newUrl1 != newUrl2);
}

test "redirect sends to correct page" {
Expand Down

0 comments on commit 2b5df31

Please sign in to comment.