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

Retrieve a Note by its id #57

Merged
merged 7 commits into from
Aug 2, 2024
Merged

Retrieve a Note by its id #57

merged 7 commits into from
Aug 2, 2024

Conversation

jhodapp
Copy link
Member

@jhodapp jhodapp commented Jul 27, 2024

Description

This PR adds the ability to retrieve a Note by its id.

GitHub Issue: None

Changes

  • Retrieve a Note by its id
  • Make sure to return an error when an invalid id is specified

Testing Strategy

  1. Using RAPIdoc, try and retrieve an existing Note by its Id (look in the DB for an existing Note's id to test)
  2. Observe that a 200 is returned with a Note JSON object
  3. Try entering a valid Id but of a Note that doesn't yet exist
  4. Observe that a 204 is returned and No Content

Concerns

None

@jhodapp jhodapp added the enhancement Improves existing functionality or feature label Jul 27, 2024
@jhodapp jhodapp requested a review from calebbourg July 27, 2024 17:22
@jhodapp jhodapp self-assigned this Jul 27, 2024
Copy link
Collaborator

@qafui qafui left a comment

Choose a reason for hiding this comment

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

Nice work!

Copy link
Collaborator

@calebbourg calebbourg left a comment

Choose a reason for hiding this comment

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

This looks great! Some of the changes bring up some great points about our conventions that might be good to discuss at this point.

I took a look at the JSON API specification for fetching resources and it outlines as convention:

  • Always returning an HTTP 200 and an empty list from endpoints that return collections when the collection is empty
  • Always returning an HTTP 200 and NULL from endpoints that return a single record when the record doesn't exist.

I think that adopting these standards makes sense considering their source as well as allowing us to simplify the BE code here and remove some of the nesting of match and other conditionals.

This makes me think we might want to update the code in our web crate to convert the RecordNotFound error to a 200 {"data": NULL}

web/src/controller/note_controller.rs Show resolved Hide resolved
@@ -37,7 +37,7 @@ pub async fn create(
// TODO: create a new Extractor to authorize the user to access
// the data requested
State(app_state): State<AppState>,
Json(note_model): Json<Model>,
Json(note_model): Json<notes::Model>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

One of my plans in the back of my head is to go back through and clean up all of the imports and define some standard for the project.

Copy link
Member Author

@jhodapp jhodapp Aug 2, 2024

Choose a reason for hiding this comment

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

That's a good idea, whichever it is we just desire consistency and I think highest (and quickest) readability, meaning with once glance you know which Model this is.

@@ -79,7 +104,27 @@ pub async fn find_by(
}
}

Ok(query.all(db).await?)
match query.all(db).await {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Disclaimer: This is from own standards in my brain which I never put on paper so take it with a grain of salt.

It was an intentional decision to return the empty collection when a query that results in a WHERE SQL clause returns 0 records. This is conventional in other ORMs that I've used.

RecordNotFound should only be returned for queries that result in a SELECT SQL statement.

The reason being is exactly so that we didn't have to do the extra check like !notes.is_empty()

I'm curious what the reasoning here is and if we could provide the same benefits without the extra nesting and conditional logic.

Copy link
Member Author

@jhodapp jhodapp Aug 2, 2024

Choose a reason for hiding this comment

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

@calebbourg You don't get an error for the empty case, so you'd always get a 200 OK which is not correct, the record isn't found. So that second match branch, i.e. Err(err) does not occur when a Note isn't found, for example, if you provide an id of a Note that doesn't exist.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm proposing that we do actually want to return a 200 when no records exist for index fetches to align with the JSON API convention.

server MUST respond to a successful request to fetch a resource collection with an array of resource objects or an empty array ([]) as the response document’s primary data.

I think the philosophy is that the request itself is still "successful" in that there were no exceptions or auth issues. We just happened to return 0 records.

Reading more into the spec I think that the 204 No Content we're mapping to RecordNotFound is not semantically inline with the HTTP standard as it states.

This might be used, for example, when implementing "save and continue editing" functionality for a wiki site. In this case a PUT request would be used to save the page, and the 204 No Content response would be sent to indicate that the editor should not be replaced by some other page.

Which I read as "something was successful here but this endpoint itself doesn't return anything meaningful". The fact that adhering to the standard yields less matching and conditional logic in the code leads me to believe that seaORM may have designed their API to support these patterns.

This is all interpretation, of course, and I'm learning these standards as we go.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I hear you on this, I think this warrants more live discussion. Just as a preview of where I'm confused: returning a 200 OK for an entity that doesn't exist seems to me to be at odds with a RESTful API.

But maybe this is just a layering issue?

HTTP is part of the OSI layer 7, and technically so is JSON. But JSON sits at a higher level than HTTP so we'd be choosing to ignore what many APIs do, which is return a 404 when you put in a URI to a resource that doesn't exist. So I think we need to discuss what layer dictates the errors around entities.

Copy link
Member Author

Choose a reason for hiding this comment

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

Now having read the JSON API spec in this area, here's what I see it saying:

https://jsonapi.org/format/#fetching-resources-responses-404

There are 3 cases:

  1. Return [] for a resource that does exist, e.g. http://localhost/articles/ but there are no articles yet but also the URI exists.
  2. Return a null for an nested entity that doesn’t exist (e.g. an article that has an author that hasn’t been defined yet) under the requested resource
  3. A request for an instance of an entity at a specific URI that doesn’t exist should return a 404

})
}
}
Err(err) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Similarly to above. when an Err() occurs from query.all(db).await, it should be propagated directly to the caller to be converted to a top level error.

@@ -57,6 +57,31 @@ pub async fn update(db: &DatabaseConnection, id: Id, model: Model) -> Result<Mod
}
}

pub async fn find_by_id(db: &DatabaseConnection, id: Id) -> Result<Option<Model>, Error> {
match Entity::find_by_id(id).one(db).await {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This makes sense here I think

…a 404 Not Found error. Also make sure an empty list of Notes is returned as an empty list and not a 404 Not Found
Copy link
Collaborator

@calebbourg calebbourg left a comment

Choose a reason for hiding this comment

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

Looks great!!

@jhodapp jhodapp merged commit b295cff into main Aug 2, 2024
4 checks passed
@jhodapp jhodapp deleted the fetch_note_by_id branch August 2, 2024 19:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Improves existing functionality or feature
Projects
Status: ✅ Done
Development

Successfully merging this pull request may close these issues.

3 participants