I am trying to make mongodb Document plain in order to apply okapi's open api which does not allow ObjectId in struct.
But I found neither building a find option like
FindOptions::builder().lean().build();
nor
colleciton.find(None, None).lean().await? works.
How do I transform MongoDB Document into JsonSchema?
Example
Before
{
_id: ObjectId,
name: String
}
After
{
_id: String,
name: String
}
You can create custom struct using ObjectId as base and the implement JsonSchema for that custom struct
use mongodb::bson::oid::ObjectId;
use schemars::JsonSchema;
use schemars::schema::Schema;
use schemars::schema::SchemaObject;
use schemars::gen::SchemaGenerator;
use serde::{Serialize, Deserialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct ObjectID(ObjectId);
impl JsonSchema for ObjectID {
fn schema_name() -> String {
stringify!(String).to_owned()
}
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema: SchemaObject = <String>::json_schema(gen).into();
schema.number().minimum = Some(1.0);
schema.into()
}
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct Customer {
pub _id: ObjectID,
pub name: String
}
fn main() {
let c = Customer{_id: ObjectID(ObjectId::new()), name: String::from("John")};
println!("{:?}", c);
let serialized = serde_json::to_string(&c).unwrap();
println!("serialized = {}", serialized);
let deserialized: Customer = serde_json::from_str(&serialized).unwrap();
println!("deserialized = {:?}", deserialized);
}
// Output
// Customer { _id: ObjectID(ObjectId("6210af6079e3adc888bef5af")), name: "John" }
// serialized = {"_id":{"$oid":"6210af6079e3adc888bef5af"},"name":"John"}
// deserialized = Customer { _id: ObjectID(ObjectId("6210af6079e3adc888bef5af")), name: "John" }
Related
I have 2 collections:
profiles
dances
struct Profile {
id: String,
firstname: String,
dances: Vec<String>
}
struct DanceModel {
id: String,
name: String,
}
I have a find query which does a lookup on the dances collection.
let cursor = profile_collection.aggregate(pipeline, None).await.unwrap();
let results = cursor.try_collect().await.unwrap_or_else(|_| vec![]);
let profile = results.first().unwrap();
let result = bson::from_document::<ProfileResult>(profile.clone());
I created a new model like so:
struct ProfileResult {
id: String,
name: String,
dances: Vec<DanceModel>
}
As you can see, the id and name fields are being duplicated, and I have 2 models for the same data.
Is there any way to avoid duplication of attributes and data models?
It looks like you are trying to apply an object concept to a procedural technology.
To work around this issue, you can create an IdName struct and apply that struct to the model in a uid field.
struct IdName {
id: String,
name: String,
}
struct Profile {
id: String,
firstname: String,
dances: Vec<String>
}
struct DanceModel {
uid: IdName
}
struct ProfileResult {
uid: IdName
dances: Vec<DanceModel>
}
A Fluent Data query with the function below works:
func indexHandler(_ req: Request) -> EventLoopFuture<[CustomerWithOrders]> {
Customer
.query(on: req.db)
.with(\.$orders)
.all()
.map { customers in
customers.map { customer in
let customerOrders = customer.orders.map {
OrderOnly(id: $0.id, date: $0.date)
}
return CustomerWithOrders(
id: customer.id,
name: customer.name,
orders: customerOrders
)
}
}
}
struct OrderOnly: Content {
let id: UUID?
let date: Date
}
struct CustomerWithOrders: Content {
let id: UUID?
let name: String
let orders: [OrderOnly]
}
but not after the exchange: [CustomerWithOrders] to View in EventLoopFuture
func indexHandler(_ req: Request) -> EventLoopFuture<View> {
Customer
.query(on: req.db)
.with(\.$orders)
.all()
.map { customers in //Type of expression is ambiguous without more context
customers.map { customer in
let customerOrders = customer.orders.map {
OrderOnly(id: $0.id, date: $0.date)
}
let context = IndexContextcwo(
id: customer.id,
name: customer.name,
orders: customerOrders
)
return req.leaf.render("home", context)
}
}
}
struct IndexContextcwo: Encodable {
let id: UUID?
let name: String
let orders: [OrderOnly]?
}
How can I get rid of the error Type of expression is ambiguous without more context" in the row ".all().map?
Thanks for a tip.
Thank you for your answers.
With changing from map to flatmap I get no further. Now an error is:
Value of type 'EventLoopFuture<[Customer]>' has no member 'flatmap'
Here are some files from the project:
WebsiteController.swift:
import Vapor
import Leaf
import Fluent
import FluentPostgresDriver
struct WebSiteController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
routes.get(use: indexHandler)
}
func indexHandler(_ req: Request)
-> EventLoopFuture<View> {
Customer.query(on: req.db).with(\.$orders)
.all().map { customers in
customers.map { customer in
let customerOrders = customer.orders.map {
OrderOnly(id: $0.id, date: $0.date)
}
let context = IndexContextcwo(id: customer.id, name: customer.name, orders: customerOrders)
return req.leaf.render("home", context)
}
}
//Test without Custommer.query ...:
//return req.leaf.render("home")
}
}
struct OrderOnly: Content {
let id: UUID?
let date: Date
}
struct CustomerWithOrders: Content {
let id: UUID?
let name: String
let orders: [OrderOnly]
}
struct IndexContextcwo: Encodable {
let id: UUID?
let name: String
let orders: [OrderOnly]?
}
Models:
Customer.swift:
import Foundation
import Vapor
import Fluent
import FluentPostgresDriver
final class Customer: Model, Content {
static let schema = "customers"
#ID
var id: UUID?
#Field(key: "name")
var name: String
#Siblings(through: OrderCustomerPivot.self, from: \.$customer, to: \.$order)
var orders: [Order]
init() {}
init(id: UUID? = nil, name: String) {
self.id = id
self.name = name
}
}
Order.swift:
import Foundation
import Vapor
import Fluent
import FluentPostgresDriver
final class Order: Model {
static let schema = "orders"
#ID
var id: UUID?
#Field(key: "date")
var date: Date
#Siblings(through: OrderCustomerPivot.self, from: \.$order, to: \.$customer)
var customers: [Customer]
init() {}
init(id: UUID? = nil, date: Date) {
self.id = id
self.date = date
}
}
extension Order: Content {}
OrderCustomerPivot.swift
import Foundation
import Fluent
final class OrderCustomerPivot: Model {
static let schema = "order-customer-pivot"
#ID
var id: UUID?
#Parent(key: "orderID")
var order: Order
#Parent(key: "customerID")
var customer: Customer
init() {}
init(id: UUID? = nil, order: Order, customer: Customer) throws {
self.id = id
self.$order.id = try order.requireID()
self.$customer.id = try customer.requireID()
}
}
The reason you get the error is because the .map means your code is expected to return a future of the same type, i.e. [CustomerWithOrders]. This is obviously correct in the first instance, but when you make the change to return the future View it isn't any longer.
From just inspecting your code, I think changing the first .map to .flatMap should fix the problem. This says you are going to return a different future, which is what you want.
I'm trying to add a POST endpoint on my Vapor 3 server to create a new resource, but I want to have 3 different JSON formats that can create that resource. So I have 3 different Content structs that are each fairly similar.
struct IDJson: Content, DeckConvertible {
var name: String?
var format: Int
var hero: Int
var cards: [Int]
}
struct NameJson: Content, DeckConvertible {
var name: String?
var format: Int
var hero: String
var cards: [String]
}
struct DeckstringJson: Content, DeckConvertible {
var name: String?
var deckstring: String
}
And I would like to add them all to my router like this
class DeckRouteController: RouteCollection {
func boot(router: Router) throws {
router.post(IDJson.self, at: "user", "collection", use: createDeckHandler)
router.post(NameJson.self, at: "user", "collection", use: createDeckHandler)
router.post(DeckstringJson.self, at: "user", "collection", use: createDeckHandler)
}
}
...
private extension DeckRouteController {
func createDeckHandler(_ request: Request, container: DeckConvertible) throws -> Future<Response> {
// Create deck
}
}
Is this possible? at the moment only the last one gets used but is there a way I can do this with some middleware handler or something?
You can handle the decoding in the function itself, ie:
router.post("user", "collection", use: createDeckHandler)
func createDeckHandler(_ request: Request) throws -> String {
let asId = try? request.content.syncDecode(IDJson.self)
let asName = try? request.content.syncDecode(NameJson.self)
if let asId = asId {
// do stuff
} else if let asName = asName {
// do something else
}
}
Just started with Vapor 3 along with a MySQL database and I am having hard time figuring out the Relations part.
I have created 2 models so far: Movie and Actor.
A Movie can have many Actors and an Actor can have many Movies.
Movie Model:
import Vapor
import FluentMySQL
final class Movie: Codable {
var id: Int?
var name: String
var synopsis: String
var dateReleased: Date
var totalGrossed: Float
init(id: Int? = nil, name: String, synopsis: String, dateReleased: Date, totalGrossed: Float) {
self.id = id
self.name = name
self.synopsis = synopsis
self.dateReleased = dateReleased
self.totalGrossed = totalGrossed
}
}
extension Movie {
var actors: Siblings<Movie, Actor, MovieActor> {
return siblings()
}
}
extension Movie: Content {}
extension Movie: Parameter {}
extension Movie: MySQLModel {}
extension Movie: MySQLMigration {
static func prepare(on conn: MySQLConnection) -> Future<Void> {
return MySQLDatabase.create(self, on: conn) { builder in
builder.field(for: \.id, isIdentifier: true)
builder.field(for: \.name)
builder.field(for: \.synopsis)
builder.field(for: \.dateReleased, type: .date)
builder.field(for: \.totalGrossed, type: .float)
}
}
}
Actor Model:
import Vapor
import FluentMySQL
final class Actor: Codable {
var id: Int?
var firstName: String
var lastName: String
var fullName: String {
return firstName + " " + lastName
}
var dateOfBirth: Date
var story: String
init(id: Int? = nil, firstName: String, lastName: String, dateOfBirth: Date, story: String) {
self.id = id
self.firstName = firstName
self.lastName = lastName
self.dateOfBirth = dateOfBirth
self.story = story
}
}
extension Actor {
var actors: Siblings<Actor, Movie, MovieActor> {
return siblings()
}
}
extension Actor: Content {}
extension Actor: Parameter {}
extension Actor: MySQLModel {}
extension Actor: MySQLMigration {
static func prepare(on conn: MySQLConnection) -> Future<Void> {
return MySQLDatabase.create(self, on: conn) { builder in
builder.field(for: \.id, isIdentifier: true)
builder.field(for: \.firstName)
builder.field(for: \.lastName)
builder.field(for: \.dateOfBirth, type: .date)
builder.field(for: \.story, type: .text)
}
}
}
And I have also created a MovieActor model as a MySQLPivot for the relationship:
import Vapor
import FluentMySQL
final class MovieActor: MySQLPivot {
typealias Left = Movie
typealias Right = Actor
static var leftIDKey: LeftIDKey = \.movieID
static var rightIDKey: RightIDKey = \.actorID
var id: Int?
var movieID: Int
var actorID: Int
init(movieID: Int, actorID: Int) {
self.movieID = movieID
self.actorID = actorID
}
}
extension MovieActor: MySQLMigration {}
And have added them to the migration section in the configure.swift file:
var migrations = MigrationConfig()
migrations.add(model: Movie.self, database: .mysql)
migrations.add(model: Actor.self, database: .mysql)
migrations.add(model: MovieActor.self, database: .mysql)
services.register(migrations)
All the tables in the database are being created just fine, but I am not receiving the relationship when calling the get all movies service. I am just receiving the Movie's properties:
final class MoviesController {
func all(request: Request) throws -> Future<[Movie]> {
return Movie.query(on: request).all()
}
}
[
{
"id": 1,
"dateReleased": "2017-11-20T00:00:00Z",
"totalGrossed": 0,
"name": "Star Wars: The Last Jedi",
"synopsis": "Someone with a lightsaber kills another person with a lightsaber"
},
{
"id": 3,
"dateReleased": "1970-07-20T00:00:00Z",
"totalGrossed": 0,
"name": "Star Wars: A New Hope",
"synopsis": "Someone new has been discovered by the force and he will kill the dark side with his awesome lightsaber and talking skills."
},
{
"id": 4,
"dateReleased": "2005-12-20T00:00:00Z",
"totalGrossed": 100000000,
"name": "Star Wars: Revenge of the Sith",
"synopsis": "Anakin Skywalker being sliced by Obi-Wan Kenobi in an epic dual of fates"
}
]
Your help would be appreciated! Thank you very much :)
So I believe you're expecting the relationship to be reflected in what is returned when you query for a Movie model. So for example you expect something like this to be returned for a Movie:
{
"id": 1,
"dateReleased": "2017-11-20T00:00:00Z",
"totalGrossed": 0,
"name": "Star Wars: The Last Jedi",
"synopsis": "Someone with a lightsaber kills another person with a lightsaber",
"actors": [
"id": 1,
"firstName": "Leonardo",
"lastName": "DiCaprio",
"dateOfBirth": "1974-11-11T00:00:00Z",
"story": "Couldn't get an Oscar until wrestling a bear for the big screen."
]
}
However, connecting the Movie and Actor models as siblings simply just gives you the convenience of being able to query the actors from a movie as if the actors were a property of the Movie model:
movie.actors.query(on: request).all()
That line above returns: Future<[Actor]>
This works vice versa for accessing the movies from an Actor object:
actor.movies.query(on: request).all()
That line above returns: Future<[Movie]>
If you wanted it to return both the movie and its actors in the same response like how I assumed you wanted it to work above, I believe the best way to do this would be creating a Content response struct like this:
struct MovieResponse: Content {
let movie: Movie
let actors: [Actor]
}
Your "all" function would now look like this:
func all(_ request: Request) throws -> Future<[MovieResponse]> {
return Movie.query(on: request).all().flatMap { movies in
let movieResponseFutures = try movies.map { movie in
try movie.actors.query(on: request).all().map { actors in
return MovieResponse(movie: movie, actors: actors)
}
}
return movieResponseFutures.flatten(on: request)
}
}
This function queries all of the movies and then iterates through each movie and then uses the "actors" sibling relation to query for that movie's actors. This actors query returns a Future<[Actor]> for each movie it queries the actors for. Map what is returned from the that relation so that you can access the actors as [Actor] instead of Future<[Actor]>, and then return that combined with the movie as a MovieResponse.
What this movieResponseFutures actually consists of is an array of MovieResponse futures:
[Future<[MovieResponse]>]
To turn that array of futures into a single future that consists of an array you use flatten(on:). This waits waits for each of those individual futures to finish and then returns them all as a single future.
If you really wanted the Actor's array inside of the Movie object json, then you could structure the MovieResponse struct like this:
struct MovieResponse: Content {
let id: Int?
let name: String
let synopsis: String
let dateReleased: Date
let totalGrossed: Float
let actors: [Actor]
init(movie: Movie, actors: [Actor]) {
self.id = movie.id
self.name = movie.name
self.synopsis = movie.synopsis
self.dateReleased = movie.dateReleased
self.totalGrossed = movie.totalGrossed
self.actors = actors
}
}
So the underlying issue here is that computed properties aren't provided in a Codable response. What you need to do is define a new type MoviesWithActors and populate that and return that. Or provide a second endpoint, something like /movies/1/actors/ that gets all the actors for a particular movie. That fits better with REST but it depends on your use case, as you may not want the extra requests etc
Using Vapor I want to store a relationship to children. I haven't been able to find any examples of what the class should look like and I'm just guessing on what to do. Can anyone provide an example of a class that has a relationship to a list of other Model objects?
import Vapor
import Fluent
import Foundation
final class Store: Model {
// MARK: - Model
var id: Node?
var exists: Bool = false
var locationIDs: [Node] = [] // No idea if this is right
var name: String
init(name: String, locationIDs: [Node] = []) {
self.id = nil
self.name = name
self.locationIDs = locationIDs
}
init(node: Node, in context: Context) throws {
id = try node.extract("id")
name = try node.extract("name")
// ???
}
func makeNode(context: Context) throws -> Node {
return try Node(node: [
"id": id,
"name": name
// ???
])
}
static func prepare(_ database: Database) throws {
try database.create( "stores" ) { creator in
creator.id()
creator.string("name")
// creator.id("", optional: false) // ???
}
}
static func revert(_ database: Database) throws {
try database.delete("stores")
}
}
Here is my implementation of a User and Group relationship. A user belongs to a group and group has many users, so a simple 1 -> Many relationship.
final public class User: Model {
public var id: Int?
var emailAddress: String
var username: String
var groupId: Int
}
extension User {
var group: Parent<User, Group> {
return parent(\.groupId)
}
}
final public class Group: Model {
public var id: Int?
var name: String
}
extension Group {
var users: Children<Group, Users> {
return children(\.groupId)
}
}
Many <-> Many are similar but use a pivot table in the middle of the relationship.