Repository: kpcyrd/sn0int (language: rust)

Issue:
Automatic fields on insert
Right now we deserialize directly into `NewOwnedUrl` (for example). We should change this to something else which is an alias to `NewOwnedUrl` or a new type that implements `Into<NewOwnedUrl>` (or something similar to return a result).

This allows us to have some fields automatically set by other fields. For example we could have a `path` field that's automatically in sync with `value`, or we could fill the `family` field in `ipaddrs` automatically.


Discussion / hints:

Fix patch:
diff --git a/migrations/2018-12-03-143558_url_path/down.sql b/migrations/2018-12-03-143558_url_path/down.sql
new file mode 100644
index 00000000..a2175747
--- /dev/null
+++ b/migrations/2018-12-03-143558_url_path/down.sql
@@ -0,0 +1,25 @@
+PRAGMA foreign_keys=off;
+
+ALTER TABLE urls RENAME TO _urls_old;
+
+CREATE TABLE urls (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    subdomain_id INTEGER NOT NULL,
+    value VARCHAR NOT NULL,
+    status INTEGER,
+    body BLOB,
+    unscoped BOOLEAN DEFAULT 0 NOT NULL,
+    online BOOLEAN,
+    title VARCHAR,
+    redirect VARCHAR,
+    FOREIGN KEY(subdomain_id) REFERENCES subdomains(id) ON DELETE CASCADE,
+    CONSTRAINT url_unique UNIQUE (value)
+);
+
+INSERT INTO urls (id, subdomain_id, value, status, body, unscoped, online, title, redirect)
+  SELECT id, subdomain_id, value, status, body, unscoped, online, title, redirect
+  FROM _urls_old;
+
+DROP TABLE _urls_old;
+
+PRAGMA foreign_keys=on;
diff --git a/migrations/2018-12-03-143558_url_path/up.sql b/migrations/2018-12-03-143558_url_path/up.sql
new file mode 100644
index 00000000..0913a47b
--- /dev/null
+++ b/migrations/2018-12-03-143558_url_path/up.sql
@@ -0,0 +1,26 @@
+PRAGMA foreign_keys=off;
+
+ALTER TABLE urls RENAME TO _urls_old;
+
+CREATE TABLE urls (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    subdomain_id INTEGER NOT NULL,
+    value VARCHAR NOT NULL,
+    path VARCHAR NOT NULL,
+    status INTEGER,
+    body BLOB,
+    unscoped BOOLEAN DEFAULT 0 NOT NULL,
+    online BOOLEAN,
+    title VARCHAR,
+    redirect VARCHAR,
+    FOREIGN KEY(subdomain_id) REFERENCES subdomains(id) ON DELETE CASCADE,
+    CONSTRAINT url_unique UNIQUE (value)
+);
+
+INSERT INTO urls (id, subdomain_id, value, path, status, body, unscoped, online, title, redirect)
+  SELECT id, subdomain_id, value, '/', status, body, unscoped, online, title, redirect
+  FROM _urls_old;
+
+DROP TABLE _urls_old;
+
+PRAGMA foreign_keys=on;
diff --git a/src/db.rs b/src/db.rs
index 5475a3aa..599667d9 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -122,6 +122,7 @@ impl Database {
             Insert::Url(object) => self.insert_struct(NewUrl {
                 subdomain_id: object.subdomain_id,
                 value: &object.value,
+                path: &object.path,
                 status: object.status,
                 body: object.body.as_ref(),
                 online: object.online,
diff --git a/src/models/domain.rs b/src/models/domain.rs
index 81c2e553..67f87f0c 100644
--- a/src/models/domain.rs
+++ b/src/models/domain.rs
@@ -221,3 +221,13 @@ impl Printable<PrintableDomain> for NewDomainOwned {
         })
     }
 }
+
+pub type InsertDomain = NewDomainOwned;
+
+impl LuaInsertToNewOwned for InsertDomain {
+    type Target = NewDomainOwned;
+
+    fn try_into_new(self) -> Result<NewDomainOwned> {
+        Ok(self)
+    }
+}
diff --git a/src/models/email.rs b/src/models/email.rs
index bf73f76f..0eb5fc23 100644
--- a/src/models/email.rs
+++ b/src/models/email.rs
@@ -209,6 +209,16 @@ impl Printable<PrintableEmail> for NewEmailOwned {
     }
 }
 
+pub type InsertEmail = NewEmailOwned;
+
+impl LuaInsertToNewOwned for InsertEmail {
+    type Target = NewEmailOwned;
+
+    fn try_into_new(self) -> Result<NewEmailOwned> {
+        Ok(self)
+    }
+}
+
 #[derive(Identifiable, AsChangeset, Serialize, Deserialize, Debug)]
 #[table_name="emails"]
 pub struct EmailUpdate {
diff --git a/src/models/ipaddr.rs b/src/models/ipaddr.rs
index cda44be3..90956f29 100644
--- a/src/models/ipaddr.rs
+++ b/src/models/ipaddr.rs
@@ -306,6 +306,16 @@ impl Printable<PrintableIpAddr> for NewIpAddrOwned {
     }
 }
 
+pub type InsertIpAddr = NewIpAddrOwned;
+
+impl LuaInsertToNewOwned for InsertIpAddr {
+    type Target = NewIpAddrOwned;
+
+    fn try_into_new(self) -> Result<NewIpAddrOwned> {
+        Ok(self)
+    }
+}
+
 #[derive(Identifiable, AsChangeset, Serialize, Deserialize, Debug)]
 #[table_name="ipaddrs"]
 pub struct IpAddrUpdate {
diff --git a/src/models/mod.rs b/src/models/mod.rs
index 68d31fe1..e3cdb82d 100644
--- a/src/models/mod.rs
+++ b/src/models/mod.rs
@@ -309,6 +309,12 @@ macro_rules! display_detailed {
     };
 }
 
+pub trait LuaInsertToNewOwned {
+    type Target;
+
+    fn try_into_new(self) -> Result<Self::Target>;
+}
+
 mod domain;
 pub use self::domain::*;
 
diff --git a/src/models/subdomain.rs b/src/models/subdomain.rs
index e97c7741..84ac927e 100644
--- a/src/models/subdomain.rs
+++ b/src/models/subdomain.rs
@@ -240,6 +240,16 @@ impl Printable<PrintableSubdomain> for NewSubdomainOwned {
     }
 }
 
+pub type InsertSubdomain = NewSubdomainOwned;
+
+impl LuaInsertToNewOwned for InsertSubdomain {
+    type Target = NewSubdomainOwned;
+
+    fn try_into_new(self) -> Result<NewSubdomainOwned> {
+        Ok(self)
+    }
+}
+
 #[derive(Identifiable, AsChangeset, Serialize, Deserialize, Debug)]
 #[table_name="subdomains"]
 pub struct SubdomainUpdate {
diff --git a/src/models/subdomain_ipaddr.rs b/src/models/subdomain_ipaddr.rs
index e29ead91..3471253b 100644
--- a/src/models/subdomain_ipaddr.rs
+++ b/src/models/subdomain_ipaddr.rs
@@ -123,3 +123,13 @@ impl Printable<PrintableSubdomainIpAddr> for NewSubdomainIpAddr {
         })
     }
 }
+
+pub type InsertSubdomainIpAddr = NewSubdomainIpAddr;
+
+impl LuaInsertToNewOwned for InsertSubdomainIpAddr {
+    type Target = NewSubdomainIpAddr;
+
+    fn try_into_new(self) -> Result<NewSubdomainIpAddr> {
+        Ok(self)
+    }
+}
diff --git a/src/models/url.rs b/src/models/url.rs
index 00c09c6e..138596cd 100644
--- a/src/models/url.rs
+++ b/src/models/url.rs
@@ -3,6 +3,7 @@ use diesel;
 use diesel::prelude::*;
 use models::*;
 use ser;
+use url;
 
 
 #[derive(Identifiable, Queryable, Associations, Serialize, Deserialize, PartialEq, Debug)]
@@ -12,6 +13,7 @@ pub struct Url {
     pub id: i32,
     pub subdomain_id: i32,
     pub value: String,
+    pub path: String,
     pub status: Option<i32>,
     pub body: Option<Vec<u8>>,
     pub unscoped: bool,
@@ -214,6 +216,7 @@ impl Detailed for Url {
 pub struct NewUrl<'a> {
     pub subdomain_id: i32,
     pub value: &'a str,
+    pub path: &'a str,
     pub status: Option<i32>,
     pub body: Option<&'a Vec<u8>>,
     pub online: Option<bool>,
@@ -254,6 +257,7 @@ impl<'a> Upsertable<Url> for NewUrl<'a> {
 pub struct NewUrlOwned {
     pub subdomain_id: i32,
     pub value: String,
+    pub path: String,
     pub status: Option<i32>,
     #[serde(deserialize_with="ser::opt_string_or_bytes")]
     pub body: Option<Vec<u8>>,
@@ -272,6 +276,38 @@ impl Printable<PrintableUrl> for NewUrlOwned {
     }
 }
 
+#[derive(Debug, Serialize, Deserialize)]
+pub struct InsertUrl {
+    pub subdomain_id: i32,
+    pub value: String,
+    pub status: Option<i32>,
+    #[serde(deserialize_with="ser::opt_string_or_bytes")]
+    pub body: Option<Vec<u8>>,
+    pub online: Option<bool>,
+    pub title: Option<String>,
+    pub redirect: Option<String>,
+}
+
+impl LuaInsertToNewOwned for InsertUrl {
+    type Target = NewUrlOwned;
+
+    fn try_into_new(self) -> Result<NewUrlOwned> {
+        let url = url::Url::parse(&self.value)?;
+        let path = url.path().to_string();
+
+        Ok(NewUrlOwned {
+            subdomain_id: self.subdomain_id,
+            value: self.value,
+            path,
+            status: self.status,
+            body: self.body,
+            online: self.online,
+            title: self.title,
+            redirect: self.redirect,
+        })
+    }
+}
+
 #[derive(Identifiable, AsChangeset, Serialize, Deserialize, Debug)]
 #[table_name="urls"]
 pub struct UrlUpdate {
diff --git a/src/runtime/db.rs b/src/runtime/db.rs
index accd93d6..21985176 100644
--- a/src/runtime/db.rs
+++ b/src/runtime/db.rs
@@ -11,6 +11,13 @@ use models::*;
 use json::LuaJsonValue;
 
 
+pub fn try_into_new<T: LuaInsertToNewOwned>(x: LuaJsonValue) -> Result<T::Target>
+    where for<'de> T: serde::Deserialize<'de>
+{
+    structs::from_lua::<T>(x)?
+        .try_into_new()
+}
+
 pub fn db_add(lua: &mut hlua::Lua, state: Arc<State>) {
     lua.set("db_add", hlua::function2(move |family: String, object: AnyLuaValue| -> Result<Option<i32>> {
         let family = Family::from_str(&family)
@@ -19,27 +26,27 @@ pub fn db_add(lua: &mut hlua::Lua, state: Arc<State>) {
 
         let object = match family {
             Family::Domain => {
-                Insert::Domain(structs::from_lua::<NewDomainOwned>(object)
+                Insert::Domain(try_into_new::<InsertDomain>(object)
                     .map_err(|e| state.set_error(e))?)
             },
             Family::Subdomain => {
-                Insert::Subdomain(structs::from_lua::<NewSubdomainOwned>(object)
+                Insert::Subdomain(try_into_new::<InsertSubdomain>(object)
                     .map_err(|e| state.set_error(e))?)
             },
             Family::IpAddr => {
-                Insert::IpAddr(structs::from_lua::<NewIpAddrOwned>(object)
+                Insert::IpAddr(try_into_new::<InsertIpAddr>(object)
                     .map_err(|e| state.set_error(e))?)
             },
             Family::SubdomainIpAddr => {
-                Insert::SubdomainIpAddr(structs::from_lua::<NewSubdomainIpAddr>(object)
+                Insert::SubdomainIpAddr(try_into_new::<InsertSubdomainIpAddr>(object)
                     .map_err(|e| state.set_error(e))?)
             },
             Family::Url => {
-                Insert::Url(structs::from_lua::<NewUrlOwned>(object)
+                Insert::Url(try_into_new::<InsertUrl>(object)
                     .map_err(|e| state.set_error(e))?)
             },
             Family::Email => {
-                Insert::Email(structs::from_lua::<NewEmailOwned>(object)
+                Insert::Email(try_into_new::<InsertEmail>(object)
                     .map_err(|e| state.set_error(e))?)
             },
         };
diff --git a/src/schema.rs b/src/schema.rs
index 129de8dc..6932535d 100644
--- a/src/schema.rs
+++ b/src/schema.rs
@@ -56,6 +56,7 @@ table! {
         id -> Integer,
         subdomain_id -> Integer,
         value -> Text,
+        path -> Text,
         status -> Nullable<Integer>,
         body -> Nullable<Binary>,
         unscoped -> Bool,


Test patch: