Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add request handler and cache #4

Merged
merged 1 commit into from
Oct 30, 2023
Merged

Conversation

madflojo
Copy link
Member

@madflojo madflojo commented Oct 30, 2023

Summary by CodeRabbit

  • New Feature: Enhanced the build process to include four new functions: init, fetch, load, and handler. These functions will improve the project's functionality and efficiency.
  • Refactor: Updated the Handler function to check the cache before querying the database for airport information, enhancing the speed of data retrieval.
  • Bug Fix: Adjusted the local_code column's data type in the airports table from VARCHAR(4) to VARCHAR(25), allowing for the storage of longer local codes.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 30, 2023

Walkthrough

The project has undergone significant changes to enhance its functionality and efficiency. The build process has been updated to include four new functions, and the Handler function has been modified to improve data retrieval and caching. Additionally, the airports database table has been altered to accommodate longer local codes.

Changes

File Summary
Makefile The build process has been updated to include four new functions: init, fetch, load, and handler. These functions are built into WebAssembly (Wasm) files using TinyGo and Docker.
functions/src/.../main.go The Handler function has been modified to parse the local_code from the payload, check the cache, query the database if necessary, and return the appropriate response. The CREATE TABLE query in the init function has been updated to change the local_code column's data type from VARCHAR(4) to VARCHAR(25).

🐇💻

Code changes here, code changes there,

In the land of code, changes are everywhere.

With a hop, skip, and a jump,

We've made improvements in one big lump.

From the build process to the database,

We've left no stone unturned in this place.

So here's to the code, ever so bright,

Making our project take flight! 🚀🌙


Tips

Chat with CodeRabbit Bot (@coderabbitai)

  • If you reply to a review comment from CodeRabbit, the bot will automatically respond.
  • To engage with CodeRabbit bot directly around the specific lines of code in the PR, mention @coderabbitai in your review comment
  • Note: Review comments are made on code diffs or files, not on the PR overview.
  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai help to get help.
  • @coderabbitai resolve to resolve all the CodeRabbit review comments.

Note: For conversation with the bot, please use the review comments on code diffs or files.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.json

@codecov
Copy link

codecov bot commented Oct 30, 2023

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (d37ed15) 92.92% compared to head (9e67936) 92.92%.
Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main       #4   +/-   ##
=======================================
  Coverage   92.92%   92.92%           
=======================================
  Files           2        2           
  Lines          99       99           
=======================================
  Hits           92       92           
  Misses          5        5           
  Partials        2        2           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 4

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 9008497 and 9e67936.
Files ignored due to filter (2)
  • config/tarmac.json
  • docker-compose.yml
Files selected for processing (3)
  • Makefile (1 hunks)
  • functions/src/handler/main.go (1 hunks)
  • functions/src/init/main.go (1 hunks)
Files skipped from review due to trivial changes (1)
  • Makefile
Additional comments: 1
functions/src/init/main.go (1)
  • 14-20: The change from VARCHAR(4) to VARCHAR(25) for the local_code column in the airports table seems reasonable if longer local codes are expected. However, ensure that this change is compatible with all the local codes that are going to be stored in this column. Also, verify that this change doesn't affect any other parts of the system that interact with this table.

Comment on lines 15 to 98
func (f *Function) Handler(payload []byte) ([]byte, error) {
return []byte("Hello World"), fmt.Errorf("not implemented http handler")
// Parse the incoming request
lc := fastjson.GetString(payload, "local_code")
if lc == "" {
return []byte(`{"error": "local_code is required"}`), fmt.Errorf("local_code is required")
}

// Lookup the airport from cache
// Lookup the airport from cache and return if found
cache, err := f.tarmac.KV.Get(lc)
if err == nil && len(cache) > 0 {
return cache, nil
}

// If not in cache, lookup the airport from the database
query := fmt.Sprintf(`SELECT * FROM airports WHERE local_code = "%s"`, html.EscapeString(lc))
data, err := f.tarmac.SQL.Query(query)
if err != nil {
return nil, fmt.Errorf("error querying database: %w", err)
}

// If not previously in cache, add to cache
// Verify we got a result
if len(data) == 0 {
return []byte(`{"error": "airport not found"}`), fmt.Errorf("airport not found")
}

// Build a response
rsp := make(map[string]string)

// Grab fields from the database and base64 decode them (this would be much easier with encoding/json)
v, err := base64.StdEncoding.DecodeString(fastjson.GetString(data, "0", "local_code"))
if err != nil {
return nil, fmt.Errorf("error decoding local_code: %w", err)
}
rsp["local_code"] = string(v)

v, err = base64.StdEncoding.DecodeString(fastjson.GetString(data, "0", "name"))
if err != nil {
return nil, fmt.Errorf("error decoding name: %w", err)
}
rsp["name"] = string(v)

v, err = base64.StdEncoding.DecodeString(fastjson.GetString(data, "0", "iso_country"))
if err != nil {
return nil, fmt.Errorf("error decoding country: %w", err)
}
rsp["country"] = string(v)

// If nothing found, return an error
v, err = base64.StdEncoding.DecodeString(fastjson.GetString(data, "0", "emoji"))
if err != nil {
return nil, fmt.Errorf("error decoding emoji: %w", err)
}
rsp["emoji"] = string(v)

v, err = base64.StdEncoding.DecodeString(fastjson.GetString(data, "0", "type"))
if err != nil {
return nil, fmt.Errorf("error decoding type: %w", err)
}
rsp["type"] = string(v)

v, err = base64.StdEncoding.DecodeString(fastjson.GetString(data, "0", "type_emoji"))
if err != nil {
return nil, fmt.Errorf("error decoding type_emoji: %w", err)
}
rsp["type_emoji"] = string(v)

v, err = base64.StdEncoding.DecodeString(fastjson.GetString(data, "0", "status"))
if err != nil {
return nil, fmt.Errorf("error decoding status: %w", err)
}
rsp["status"] = string(v)

// Create JSON response
j := fmt.Sprintf(`{"local_code": "%s", "name": "%s", "country": "%s", "emoji": "%s", "type": "%s", "type_emoji": "%s", "status": "%s"}`,
rsp["local_code"], rsp["name"], rsp["country"], rsp["emoji"], rsp["type"], rsp["type_emoji"], rsp["status"])

// If not previously in cache, add to cache
err = f.tarmac.KV.Set(lc, []byte(j))
if err != nil {
f.tarmac.Logger.Error(fmt.Sprintf("error setting cache value: %w", err))
}

// Return the airport
return nil, nil
return []byte(j), nil
}
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Handler function is well-structured and follows a logical flow. It first checks the cache for the requested data, then queries the database if the data is not found in the cache. It also handles errors appropriately and returns meaningful error messages. However, the function could be refactored to improve readability and maintainability. Consider breaking down the function into smaller, more manageable functions. For example, you could have separate functions for parsing the request, checking the cache, querying the database, and building the response. This would make the code easier to read and maintain, and it would also make it easier to test individual components of the function.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fair.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@madflojo I'm glad you found the feedback helpful. Here's a possible way to refactor the Handler function:

  1. Parse Request Function: This function will take the payload as input and return the local_code. It will handle the case where local_code is not provided and return an appropriate error.

  2. Check Cache Function: This function will take the local_code as input and return the cached data if it exists. If the data is not in the cache, it will return a specific error indicating that the data was not found in the cache.

  3. Query Database Function: This function will take the local_code as input and return the data from the database. It will handle any errors that occur during the database query.

  4. Build Response Function: This function will take the database data as input and return a response. It will handle the base64 decoding and the creation of the JSON response.

  5. Set Cache Function: This function will take the local_code and the JSON response as input and add the data to the cache. It will handle any errors that occur during this process.

By breaking down the Handler function into these smaller functions, the code will be easier to read and maintain. Each function will have a single responsibility, making it easier to understand what each part of the code is doing. Additionally, this will make it easier to write tests for each individual function, improving the overall test coverage and reliability of your code.

Let me know if you need further clarification or assistance with this refactoring process.

functions/src/handler/main.go Show resolved Hide resolved
Comment on lines +44 to +84
v, err := base64.StdEncoding.DecodeString(fastjson.GetString(data, "0", "local_code"))
if err != nil {
return nil, fmt.Errorf("error decoding local_code: %w", err)
}
rsp["local_code"] = string(v)

v, err = base64.StdEncoding.DecodeString(fastjson.GetString(data, "0", "name"))
if err != nil {
return nil, fmt.Errorf("error decoding name: %w", err)
}
rsp["name"] = string(v)

v, err = base64.StdEncoding.DecodeString(fastjson.GetString(data, "0", "iso_country"))
if err != nil {
return nil, fmt.Errorf("error decoding country: %w", err)
}
rsp["country"] = string(v)

// If nothing found, return an error
v, err = base64.StdEncoding.DecodeString(fastjson.GetString(data, "0", "emoji"))
if err != nil {
return nil, fmt.Errorf("error decoding emoji: %w", err)
}
rsp["emoji"] = string(v)

v, err = base64.StdEncoding.DecodeString(fastjson.GetString(data, "0", "type"))
if err != nil {
return nil, fmt.Errorf("error decoding type: %w", err)
}
rsp["type"] = string(v)

v, err = base64.StdEncoding.DecodeString(fastjson.GetString(data, "0", "type_emoji"))
if err != nil {
return nil, fmt.Errorf("error decoding type_emoji: %w", err)
}
rsp["type_emoji"] = string(v)

v, err = base64.StdEncoding.DecodeString(fastjson.GetString(data, "0", "status"))
if err != nil {
return nil, fmt.Errorf("error decoding status: %w", err)
}
rsp["status"] = string(v)
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The base64 decoding is repeated for each field. This is a violation of the DRY (Don't Repeat Yourself) principle. Consider creating a helper function to handle the base64 decoding. This would make the code cleaner and easier to maintain. Here is an example of how you could do this:

func decodeBase64(data string) (string, error) {
	v, err := base64.StdEncoding.DecodeString(data)
	if err != nil {
		return "", fmt.Errorf("error decoding data: %w", err)
	}
	return string(v), nil
}

Then you can use this function to decode each field:

rsp["local_code"], err = decodeBase64(fastjson.GetString(data, "0", "local_code"))
// handle error
rsp["name"], err = decodeBase64(fastjson.GetString(data, "0", "name"))
// handle error
// and so on...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai, that would result in more code as you still have to check the error of your function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@madflojo, you're correct that the error still needs to be checked after each function call. However, the proposed helper function decodeBase64 encapsulates the base64 decoding logic, making the main function cleaner and more readable.

While it might seem like more code at first glance, it actually simplifies the process of decoding and error handling. It also adheres to the DRY principle, which is a best practice in software development. This principle reduces the chance of bugs and makes the code easier to maintain.

Here's how you could handle the errors with the helper function:

rsp["local_code"], err = decodeBase64(fastjson.GetString(data, "0", "local_code"))
if err != nil {
    return nil, fmt.Errorf("error decoding local_code: %w", err)
}
rsp["name"], err = decodeBase64(fastjson.GetString(data, "0", "name"))
if err != nil {
    return nil, fmt.Errorf("error decoding name: %w", err)
}
// and so on...

This way, the base64 decoding and error handling logic is clear and consistent across all fields.

functions/src/handler/main.go Show resolved Hide resolved
@madflojo madflojo merged commit 92be317 into tarmac-project:main Oct 30, 2023
4 checks passed
@madflojo madflojo deleted the parsecsv branch October 30, 2023 01:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant