I'm writing a simple bookmark manager which will save data in a JSON format like this;
{
"children": [
{
"name": "Social",
"children": [
{
"name": "Facebook",
"url": "https://facebook.com",
"faviconUrl": "",
"tags": [],
"keyword": "",
"createdAt": 8902351,
"modifiedAt": 90981235
}
],
"createdAt": 235123534,
"modifiedAt": 23531235
}
]
}
I've tried to write children field to allow two possible types (Directory and Bookmark) by creating a common Entry trait but I'm hitting a wall as I can't implement Serialize trait from serde for the Entry trait.
use serde::{Serialize, Deserialize, Serializer};
#[derive(Serialize, Deserialize)]
struct Root<'a> {
children: Vec<&'a dyn Entry>,
}
#[derive(Serialize, Deserialize)]
struct Directory<'a> {
name: String,
created_at: u64,
children: Vec<&'a dyn Entry>,
modified_at: u64
}
#[derive(Serialize, Deserialize)]
struct Bookmark {
name: String,
url: String,
favicon_url: String,
tags: Vec<String>,
keyword: String,
created_at: u64,
modified_at: u64,
}
trait Entry {
fn is_directory(&self) -> bool;
}
impl Entry for Directory<'_> {
fn is_directory(&self) -> bool {
true
}
}
impl Entry for Bookmark {
fn is_directory(&self) -> bool {
false
}
}
// can't do this
impl Serialize for Entry {}
Is it even possible to make this work or I should create a different structure which wouldn't contain a field with multiple possible values? I was thinking about loading the JSON as HashMap<String, serde_json::Value> and looping through the hash map but I was wondering if there is some more elegant way to do this.
If you just make the Entry an enum rather than a trait, and change the &dyn Entry to just Entry, then everything should just work, except you will end up with one extra level in your JSON, and an additional tag entry telling you what type the entry is. As Masklinn pointed out in the comments, the case is also incorrect, but can be fixed using #[serde(rename_all = "camelCase")].
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Root {
children: Vec<Entry>,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Directory {
name: String,
created_at: u64,
children: Vec<Entry>,
modified_at: u64,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Bookmark {
name: String,
url: String,
favicon_url: String,
tags: Vec<String>,
keyword: String,
created_at: u64,
modified_at: u64,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum Entry {
Directory(Directory),
Bookmark(Bookmark),
}
If you really don't want the extra level and the tag, then you can use the serde(untagged) annotation to Entry.
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)]
enum Entry {
Directory(Directory),
Bookmark(Bookmark),
}
If you need a bit more flexibility you can create an intermediate struct BookmarkOrDirectory that contains all the fields of both, with the fields that only occur in one as Option and then implement TryFrom<BookmarkOrDirectory> for Entry and use serde(try_from=...) and serde(into=...) to convert to/from the appropriate form. An example implementation is below. It compiles, but has a few todo! scattered in it, and uses String as an error type, which is hacky - and is of course untested.
use core::convert::TryFrom;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct Root {
children: Vec<Entry>,
}
#[derive(Clone)]
struct Directory {
name: String,
created_at: u64,
children: Vec<Entry>,
modified_at: u64,
}
#[derive(Clone)]
struct Bookmark {
name: String,
url: String,
favicon_url: String,
tags: Vec<String>,
keyword: String,
created_at: u64,
modified_at: u64,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(try_from = "BookmarkOrDirectory", into = "BookmarkOrDirectory")]
enum Entry {
Directory(Directory),
Bookmark(Bookmark),
}
#[derive(Serialize, Deserialize)]
struct BookmarkOrDirectory {
name: String,
url: Option<String>,
favicon_url: Option<String>,
tags: Option<Vec<String>>,
keyword: Option<String>,
created_at: u64,
modified_at: u64,
children: Option<Vec<Entry>>,
}
impl BookmarkOrDirectory {
pub fn to_directory(self) -> Result<Directory, (Self, String)> {
// Check all the fields are there
if !self.children.is_some() {
return Err((self, "children is not set".to_string()));
}
// TODO: Check extra fields are not there
Ok(Directory {
name: self.name,
created_at: self.created_at,
children: self.children.unwrap(),
modified_at: self.modified_at,
})
}
pub fn to_bookmark(self) -> Result<Bookmark, (Self, String)> {
todo!()
}
}
impl TryFrom<BookmarkOrDirectory> for Entry {
type Error = String;
fn try_from(v: BookmarkOrDirectory) -> Result<Self, String> {
// Try to parse it as direcory
match v.to_directory() {
Ok(directory) => Ok(Entry::Directory(directory)),
Err((v, mesg1)) => {
// if that fails try to parse it as bookmark
match v.to_bookmark() {
Ok(bookmark) => Ok(Entry::Bookmark(bookmark)),
Err((_v, mesg2)) => Err(format!("unable to convert to entry - not a bookmark since '{}', not a directory since '{}'", mesg2, mesg1))
}
}
}
}
}
impl Into<BookmarkOrDirectory> for Bookmark {
fn into(self) -> BookmarkOrDirectory {
todo!()
}
}
impl Into<BookmarkOrDirectory> for Directory {
fn into(self) -> BookmarkOrDirectory {
todo!()
}
}
impl Into<BookmarkOrDirectory> for Entry {
fn into(self) -> BookmarkOrDirectory {
match self {
Entry::Bookmark(bookmark) => bookmark.into(),
Entry::Directory(directory) => directory.into(),
}
}
}
I have the file "user.rs" that has the struct of a postgres database table. Whenever I try to include it in my main.rs file (A Rocket web project), all of the Diesel "stuff" can't resolve. Here is my user.js file:
use super::schema::users;
pub mod handler;
pub mod repository;
pub mod router;
#[derive(Queryable, AsChangeset, Serialize, Deserialize)]
#[table_name = "users"]
pub struct User {
pub id: String,
pub username: String,
pub password: String,
}
#[derive(Insertable)]
#[table_name = "users"]
pub struct InsertableUser {
username: String,
password: String,
}
pub impl InsertableUser {
pub fn from_user(user: User) -> InsertableUser {
InsertableUser {
username: user.username,
password: user.password,
}
}
}
pub fn all(connection: &PgConnection) -> QueryResult<Vec<User>> {
users::table.load::<User>(&*connection)
}
pub fn get(id: i32, connection: &PgConnection) -> QueryResult<User> {
users::table.find(id).get_result::<User>(connection)
}
pub fn insert(user: User, connection: &PgConnection) -> QueryResult<User> {
diesel::insert_into(users::table)
.values(&InsertableUser::from_user(user))
.get_result(connection)
}
pub fn update(id: i32, user: User, connection: &PgConnection) -> QueryResult<User> {
diesel::update(users::table.find(id))
.set(&user)
.get_result(connection)
}
pub fn delete(id: i32, connection: &PgConnection) -> QueryResult<usize> {
diesel::delete(users::table.find(id)).execute(connection)
}
And here is my main.rs:
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use]
extern crate rocket;
#[macro_use]
extern crate rocket_contrib;
use rocket_contrib::databases::diesel;
#[database("camera-server-db")]
struct CameraServerDbConn(diesel::PgConnection);
mod user;
#[get("/")]
fn index() -> &'static str {
"Hello World!"
}
fn main() {
rocket::ignite()
.attach(CameraServerDbConn::fairing())
.mount("/", routes![index])
.launch();
}
If I remove mod user; from main.rs, no error show up. When I run cargo check, I get many "cannot find x in this scope". Here's an example:
error: cannot find derive macro `AsChangeset` in this scope
--> src/user.rs:7:21
|
7 | #[derive(Queryable, AsChangeset, Serialize, Deserialize)]
| ^^^^^^^^^^^
I'm trying to follow this guide (which is admittedly quite out of date, but it was one of the only actual guides I could find).
As mentioned in the linked guide in the section "The last step", you need to import diesel correctly otherwise the compiler cannot resolve those traits/derives/functions. That means you need to change your main.rs file as following:
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use]
extern crate rocket;
#[macro_use]
extern crate rocket_contrib;
#[macro_use]
extern crate diesel;
use rocket_contrib::databases::diesel;
#[database("camera-server-db")]
struct CameraServerDbConn(diesel::PgConnection);
mod user;
#[get("/")]
fn index() -> &'static str {
"Hello World!"
}
fn main() {
rocket::ignite()
.attach(CameraServerDbConn::fairing())
.mount("/", routes![index])
.launch();
}
(note the additional #[macro_use] extern crate diesel; in your extern crate section.)
Playground link
use serde_json::json; // 1.0.57
fn main() {
let users = vec![Users {
id : 10,
username : "test".to_string(),
password : "pass".to_string()
}];
for user in &users {
println!("I print id:{},password:{},username:{} ",user.id, user.password, user.username);
}
println!("json_serde prints {}",json!(&users));
let serialized = serde_json::to_string(&users).unwrap();
println!("Different serde: {}",serialized);
}
#[derive(Serialize, Deserialize)]
pub struct Users {
pub id: i32,
pub username: String,
pub password: String,
}
It works perfect but in my server i get this
I print id:4,password:test, username:test
json_serde prints [{"id":4,"password":"test\r\n","username":"test\r"}]
The only difference is I get my data from the db
Diesel query
let users = users
.filter(id.eq(p_id))
.limit(10)
.load::<Users>(&connection)
.expect("Error loading posts"),
Actual Users in model.rs
#[derive(Queryable)]
#[derive(Serialize, Deserialize)]
pub struct Users {
pub id: i32,
pub username: String,
pub password: String,
}
shema.rs
table! {
users (id) {
id -> Int4,
username -> Varchar,
password -> Varchar,
}
}
I forget to trim my input so serde_json was correct
I have a struct:
#[derive(Serialize,Deserialize,Debug)]
struct Post {
#[serde(rename(deserialize = "userId"))]
user_id: i32,
id: i32,
title: String,
body: String,
}
I need to deserialize JSON to Vec<Post>:
extern crate restson;
extern crate serde;
extern crate serde_derive;
use std::fs;
use std::path::Path;
use restson::{RestClient, RestPath, Error};
use serde_derive::{Serialize, Deserialize};
const URI: &str ="https://jsonplaceholder.typicode.com/";
impl RestPath<()> for Vec<Post> {
fn get_path(_: ()) -> Result<String, Error> {
Ok(String::from("posts"))
}
}
fn main() {
let mut client = RestClient::new(URI).unwrap();
let posts: Vec<Post> = client.get(()).unwrap();
println!("{:?}", posts.len());
}
I'm totally new, so please help me.
I'm trying to use the #[primarykey()] macro in Diesel but am getting an error that it is unknown. From what I have found, adding #![feature(primary_key)] should solve the issue, but it doesn't.
lib.rs
#[macro_use]
extern crate diesel;
extern crate dotenv;
pub mod schema;
pub mod models;
use diesel::prelude::*;
use diesel::pg::PgConnection;
use dotenv::dotenv;
use std::env;
pub fn establish_connection() -> PgConnection {
dotenv().ok();
let database_url = env::var("DATABASE_URL")
.expect("DATABASE_URL must be set");
PgConnection::establish(&database_url)
.expect(&format!("Error connecting to {}", database_url))
}
models.rs
#![feature(primary_key)]
extern crate diesel;
#[derive(Queryable, Debug)]
#[primary_key(user_id)]
pub struct User {
pub user_id: i32,
pub email: String,
pub password: String,
pub bio: String,
pub verified: bool,
}
I've also tried adding #![feature(primary_key)] to the top of lib.rs without any luck.
Using Rust 1.26.0-nightly (80785a547 2018-03-30)
The primary_key attribute is only applicable when deriving Identifiable:
#[macro_use]
extern crate diesel;
mod schema {
table! {
users (user_id) {
user_id -> Int4,
email -> Text,
}
}
#[derive(Debug, Identifiable)]
#[primary_key(email)]
pub struct User {
pub user_id: i32,
pub email: String,
}
}
fn main() {}
I believe you could also just change your primary key in your schema definition (users (user_id)).