Repository: ron-rs/ron (rust)

Issue:
Move from Travis to GH actions
Travis (.org specifically) has been having a hard time lately.

Pull request description:
Replace Travis with GHA
As [mentioned here](https://github.com/ron-rs/ron/pull/222#issuecomment-622598923). Closes #213

Patch:
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 00000000..983cfb6e
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,56 @@
+name: CI
+on:
+  push:
+    branches:
+      - staging
+      - trying
+      - master
+
+jobs:
+
+  tests:
+    name: Tests
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        rust: [stable, nightly]
+
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions-rs/toolchain@v1
+        with:
+          toolchain: ${{ matrix.rust }}
+          profile: minimal
+          override: true
+      - run: cargo test
+      - run: cargo test --all-features
+
+  clippy:
+    name: "Clippy: stable"
+    runs-on: ubuntu-latest
+    continue-on-error: true
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+          profile: minimal
+          components: clippy
+          override: true
+      - uses: actions-rs/clippy-check@v1
+        with:
+          token: ${{ github.token }}
+  
+  rustfmt:
+    name: "Format: stable"
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+          profile: minimal
+          components: rustfmt
+          override: true
+      - run: cargo fmt -- --check
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 177f0224..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-language: rust
-
-rust:
-  - stable
-  - nightly
-
-branches:
-  only:
-    - staging
-    - trying
-    - master
-
-matrix:
-  allow_failures:
-    - name: "Clippy: stable"
-  include:
-    - rust: stable
-      name: "Format: stable"
-      script:
-        - rustup component add rustfmt
-        - cargo fmt -- --check
-    - rust: stable
-      name: "Clippy: stable"
-      script:
-        - rustup component add clippy
-        - cargo clippy
-
-cache: cargo
-
-script:
-  - cargo test
-  - cargo test --all-features
-
diff --git a/bors.toml b/bors.toml
index 5c15d322..b18c63c6 100644
--- a/bors.toml
+++ b/bors.toml
@@ -1,5 +1,6 @@
 status = [
-  "continuous-integration/travis-ci/push",
+  "Tests",
+  "Format: stable",
 ]
 
 block_labels = [
diff --git a/src/de/error.rs b/src/de/error.rs
deleted file mode 100644
index 3794cc4d..00000000
--- a/src/de/error.rs
+++ /dev/null
@@ -1,100 +0,0 @@
-use serde::de;
-use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error};
-
-use crate::parse::Position;
-
-/// Deserialization result.
-pub type Result<T> = std::result::Result<T, Error>;
-
-#[derive(Clone, Debug, PartialEq)]
-pub enum Error {
-    IoError(String),
-    Message(String),
-    Parser(ParseError, Position),
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub enum ParseError {
-    Base64Error(base64::DecodeError),
-    Eof,
-    ExpectedArray,
-    ExpectedArrayEnd,
-    ExpectedAttribute,
-    ExpectedAttributeEnd,
-    ExpectedBoolean,
-    ExpectedComma,
-    ExpectedEnum,
-    ExpectedChar,
-    ExpectedFloat,
-    ExpectedInteger,
-    ExpectedOption,
-    ExpectedOptionEnd,
-    ExpectedMap,
-    ExpectedMapColon,
-    ExpectedMapEnd,
-    ExpectedStruct,
-    ExpectedStructEnd,
-    ExpectedUnit,
-    ExpectedStructName,
-    ExpectedString,
-    ExpectedStringEnd,
-    ExpectedIdentifier,
-
-    InvalidEscape(&'static str),
-
-    IntegerOutOfBounds,
-
-    NoSuchExtension(String),
-
-    UnclosedBlockComment,
-    UnderscoreAtBeginning,
-    UnexpectedByte(char),
-
-    Utf8Error(Utf8Error),
-    TrailingCharacters,
-
-    #[doc(hidden)]
-    __NonExhaustive,
-}
-
-impl fmt::Display for Error {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match *self {
-            Error::IoError(ref s) => write!(f, "{}", s),
-            Error::Message(ref s) => write!(f, "{}", s),
-            Error::Parser(_, pos) => write!(f, "{}: {}", pos, self),
-        }
-    }
-}
-
-impl de::Error for Error {
-    fn custom<T: fmt::Display>(msg: T) -> Self {
-        Error::Message(msg.to_string())
-    }
-}
-
-impl StdError for Error {}
-
-impl From<Utf8Error> for ParseError {
-    fn from(e: Utf8Error) -> Self {
-        ParseError::Utf8Error(e)
-    }
-}
-
-impl From<FromUtf8Error> for ParseError {
-    fn from(e: FromUtf8Error) -> Self {
-        ParseError::Utf8Error(e.utf8_error())
-    }
-}
-
-impl From<Utf8Error> for Error {
-    fn from(e: Utf8Error) -> Self {
-        Error::Parser(ParseError::Utf8Error(e), Position { line: 0, col: 0 })
-    }
-}
-
-impl From<io::Error> for Error {
-    fn from(e: io::Error) -> Self {
-        Error::IoError(e.to_string())
-    }
-}
diff --git a/src/de/mod.rs b/src/de/mod.rs
index 34080eef..e3bf242a 100644
--- a/src/de/mod.rs
+++ b/src/de/mod.rs
@@ -1,5 +1,5 @@
 /// Deserialization module.
-pub use self::error::{Error, ParseError, Result};
+pub use crate::error::{Error, ErrorCode, Result};
 pub use crate::parse::Position;
 
 use serde::de::{self, DeserializeSeed, Deserializer as SerdeError, Visitor};
@@ -10,7 +10,6 @@ use self::tag::TagDeserializer;
 use crate::extensions::Extensions;
 use crate::parse::{AnyNum, Bytes, ParsedStr};
 
-mod error;
 mod id;
 mod tag;
 #[cfg(test)]
@@ -88,7 +87,7 @@ impl<'de> Deserializer<'de> {
         if self.bytes.bytes().is_empty() {
             Ok(())
         } else {
-            self.bytes.err(ParseError::TrailingCharacters)
+            self.bytes.err(ErrorCode::TrailingCharacters)
         }
     }
 
@@ -179,7 +178,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
             b'.' => self.deserialize_f64(visitor),
             b'"' | b'r' => self.deserialize_string(visitor),
             b'\'' => self.deserialize_char(visitor),
-            other => self.bytes.err(ParseError::UnexpectedByte(other as char)),
+            other => self.bytes.err(ErrorCode::UnexpectedByte(other as char)),
         }
     }
 
@@ -320,7 +319,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
 
         match res {
             Ok(byte_buf) => visitor.visit_byte_buf(byte_buf),
-            Err(err) => self.bytes.err(ParseError::Base64Error(err)),
+            Err(err) => self.bytes.err(ErrorCode::Base64Error(err)),
         }
     }
 
@@ -345,10 +344,10 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
             if self.bytes.consume(")") {
                 Ok(v)
             } else {
-                self.bytes.err(ParseError::ExpectedOptionEnd)
+                self.bytes.err(ErrorCode::ExpectedOptionEnd)
             }
         } else {
-            self.bytes.err(ParseError::ExpectedOption)
+            self.bytes.err(ErrorCode::ExpectedOption)
         }
     }
 
@@ -360,7 +359,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
         if self.bytes.consume("()") {
             visitor.visit_unit()
         } else {
-            self.bytes.err(ParseError::ExpectedUnit)
+            self.bytes.err(ErrorCode::ExpectedUnit)
         }
     }
 
@@ -395,10 +394,10 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
             if self.bytes.consume(")") {
                 Ok(value)
             } else {
-                self.bytes.err(ParseError::ExpectedStructEnd)
+                self.bytes.err(ErrorCode::ExpectedStructEnd)
             }
         } else {
-            self.bytes.err(ParseError::ExpectedStruct)
+            self.bytes.err(ErrorCode::ExpectedStruct)
         }
     }
 
@@ -413,10 +412,10 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
             if self.bytes.consume("]") {
                 Ok(value)
             } else {
-                self.bytes.err(ParseError::ExpectedArrayEnd)
+                self.bytes.err(ErrorCode::ExpectedArrayEnd)
             }
         } else {
-            self.bytes.err(ParseError::ExpectedArray)
+            self.bytes.err(ErrorCode::ExpectedArray)
         }
     }
 
@@ -431,10 +430,10 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
             if self.bytes.consume(")") {
                 Ok(value)
             } else {
-                self.bytes.err(ParseError::ExpectedArrayEnd)
+                self.bytes.err(ErrorCode::ExpectedArrayEnd)
             }
         } else {
-            self.bytes.err(ParseError::ExpectedArray)
+            self.bytes.err(ErrorCode::ExpectedArray)
         }
     }
 
@@ -462,10 +461,10 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
             if self.bytes.consume("}") {
                 Ok(value)
             } else {
-                self.bytes.err(ParseError::ExpectedMapEnd)
+                self.bytes.err(ErrorCode::ExpectedMapEnd)
             }
         } else {
-            self.bytes.err(ParseError::ExpectedMap)
+            self.bytes.err(ErrorCode::ExpectedMap)
         }
     }
 
@@ -489,10 +488,10 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
             if self.bytes.consume(")") {
                 Ok(value)
             } else {
-                self.bytes.err(ParseError::ExpectedStructEnd)
+                self.bytes.err(ErrorCode::ExpectedStructEnd)
             }
         } else {
-            self.bytes.err(ParseError::ExpectedStruct)
+            self.bytes.err(ErrorCode::ExpectedStruct)
         }
     }
 
@@ -540,7 +539,7 @@ impl<'a, 'de> CommaSeparated<'a, 'de> {
         }
     }
 
-    fn err<T>(&self, kind: ParseError) -> Result<T> {
+    fn err<T>(&self, kind: ErrorCode) -> Result<T> {
         self.de.bytes.err(kind)
     }
 
@@ -604,7 +603,7 @@ impl<'de, 'a> de::MapAccess<'de> for CommaSeparated<'a, 'de> {
 
             Ok(res)
         } else {
-            self.err(ParseError::ExpectedMapColon)
+            self.err(ErrorCode::ExpectedMapColon)
         }
     }
 }
@@ -658,10 +657,10 @@ impl<'de, 'a> de::VariantAccess<'de> for Enum<'a, 'de> {
             if self.de.bytes.consume(")") {
                 Ok(val)
             } else {
-                self.de.bytes.err(ParseError::ExpectedStructEnd)
+                self.de.bytes.err(ErrorCode::ExpectedStructEnd)
             }
         } else {
-            self.de.bytes.err(ParseError::ExpectedStruct)
+            self.de.bytes.err(ErrorCode::ExpectedStruct)
         }
     }
 
diff --git a/src/de/value.rs b/src/de/value.rs
index e49fad9a..a9ba017e 100644
--- a/src/de/value.rs
+++ b/src/de/value.rs
@@ -229,11 +229,14 @@ mod tests {
 
     #[test]
     fn test_tuples_error() {
-        use crate::de::{Error, ParseError, Position};
+        use crate::de::{Error, ErrorCode, Position};
 
         assert_eq!(
             Value::from_str("Foo:").unwrap_err(),
-            Error::Parser(ParseError::TrailingCharacters, Position { col: 4, line: 1 }),
+            Error {
+                code: ErrorCode::TrailingCharacters,
+                position: Position { col: 4, line: 1 }
+            },
         );
     }
 
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 00000000..494561df
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,161 @@
+use serde::{de, ser};
+use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error};
+
+pub use crate::parse::Position;
+
+/// This type represents all possible errors that can occur when
+/// serializing or deserializing RON data.
+#[derive(Clone, Debug, PartialEq)]
+pub struct Error {
+    pub code: ErrorCode,
+    pub position: Position,
+}
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[non_exhaustive]
+#[derive(Clone, Debug, PartialEq)]
+pub enum ErrorCode {
+    Io(String),
+    Message(String),
+    Base64Error(base64::DecodeError),
+    Eof,
+    ExpectedArray,
+    ExpectedArrayEnd,
+    ExpectedAttribute,
+    ExpectedAttributeEnd,
+    ExpectedBoolean,
+    ExpectedComma,
+    // ExpectedEnum,
+    ExpectedChar,
+    ExpectedFloat,
+    ExpectedInteger,
+    ExpectedOption,
+    ExpectedOptionEnd,
+    ExpectedMap,
+    ExpectedMapColon,
+    ExpectedMapEnd,
+    ExpectedStruct,
+    ExpectedStructEnd,
+    ExpectedUnit,
+    // ExpectedStructName,
+    ExpectedString,
+    ExpectedStringEnd,
+    ExpectedIdentifier,
+
+    InvalidEscape(&'static str),
+
+    IntegerOutOfBounds,
+
+    NoSuchExtension(String),
+
+    UnclosedBlockComment,
+    UnderscoreAtBeginning,
+    UnexpectedByte(char),
+
+    Utf8Error(Utf8Error),
+    TrailingCharacters,
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if (self.position == Position { line: 0, col: 0 }) {
+            write!(f, "{}", self.code)
+        } else {
+            write!(f, "{}: {}", self.position, self.code)
+        }
+    }
+}
+
+impl fmt::Display for ErrorCode {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match *self {
+            ErrorCode::Io(ref s) => f.write_str(s),
+            ErrorCode::Message(ref s) => f.write_str(s),
+            ErrorCode::Base64Error(ref e) => fmt::Display::fmt(e, f),
+            ErrorCode::Eof => f.write_str("Unexpected end of file"),
+            ErrorCode::ExpectedArray => f.write_str("Expected array"),
+            ErrorCode::ExpectedArrayEnd => f.write_str("Expected end of array"),
+            ErrorCode::ExpectedAttribute => f.write_str("Expected an enable attribute"),
+            ErrorCode::ExpectedAttributeEnd => {
+                f.write_str("Expected closing `)` and `]` after the attribute")
+            }
+            ErrorCode::ExpectedBoolean => f.write_str("Expected boolean"),
+            ErrorCode::ExpectedComma => f.write_str("Expected comma"),
+            // ErrorCode::ExpectedEnum => f.write_str("Expected enum"),
+            ErrorCode::ExpectedChar => f.write_str("Expected char"),
+            ErrorCode::ExpectedFloat => f.write_str("Expected float"),
+            ErrorCode::ExpectedInteger => f.write_str("Expected integer"),
+            ErrorCode::ExpectedOption => f.write_str("Expected option"),
+            ErrorCode::ExpectedOptionEnd => f.write_str("Expected end of option"),
+            ErrorCode::ExpectedMap => f.write_str("Expected map"),
+            ErrorCode::ExpectedMapColon => f.write_str("Expected colon"),
+            ErrorCode::ExpectedMapEnd => f.write_str("Expected end of map"),
+            ErrorCode::ExpectedStruct => f.write_str("Expected struct"),
+            ErrorCode::ExpectedStructEnd => f.write_str("Expected end of struct"),
+            ErrorCode::ExpectedUnit => f.write_str("Expected unit"),
+            // ErrorCode::ExpectedStructName => f.write_str("Expected struct name"),
+            ErrorCode::ExpectedString => f.write_str("Expected string"),
+            ErrorCode::ExpectedStringEnd => f.write_str("Expected string end"),
+            ErrorCode::ExpectedIdentifier => f.write_str("Expected identifier"),
+            ErrorCode::InvalidEscape(_) => f.write_str("Invalid escape sequence"),
+            ErrorCode::IntegerOutOfBounds => f.write_str("Integer is out of bounds"),
+            ErrorCode::NoSuchExtension(_) => f.write_str("No such RON extension"),
+            ErrorCode::Utf8Error(ref e) => fmt::Display::fmt(e, f),
+            ErrorCode::UnclosedBlockComment => f.write_str("Unclosed block comment"),
+            ErrorCode::UnderscoreAtBeginning => f.write_str("Found underscore at the beginning"),
+            ErrorCode::UnexpectedByte(_) => f.write_str("Unexpected byte"),
+            ErrorCode::TrailingCharacters => f.write_str("Non-whitespace trailing characters"),
+        }
+    }
+}
+
+impl de::Error for Error {
+    fn custom<T: fmt::Display>(msg: T) -> Self {
+        Error {
+            code: ErrorCode::Message(msg.to_string()),
+            position: Position { line: 0, col: 0 },
+        }
+    }
+}
+
+impl ser::Error for Error {
+    fn custom<T: fmt::Display>(msg: T) -> Self {
+        Error {
+            code: ErrorCode::Message(msg.to_string()),
+            position: Position { line: 0, col: 0 },
+        }
+    }
+}
+
+impl StdError for Error {}
+
+impl From<Utf8Error> for ErrorCode {
+    fn from(e: Utf8Error) -> Self {
+        ErrorCode::Utf8Error(e)
+    }
+}
+
+impl From<FromUtf8Error> for ErrorCode {
+    fn from(e: FromUtf8Error) -> Self {
+        ErrorCode::Utf8Error(e.utf8_error())
+    }
+}
+
+impl From<Utf8Error> for Error {
+    fn from(e: Utf8Error) -> Self {
+        Error {
+            code: ErrorCode::Utf8Error(e),
+            position: Position { line: 0, col: 0 },
+        }
+    }
+}
+
+impl From<io::Error> for Error {
+    fn from(e: io::Error) -> Self {
+        Error {
+            code: ErrorCode::Io(e.to_string()),
+            position: Position { line: 0, col: 0 },
+        }
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index d5fed6d2..9d2d176b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -58,9 +58,16 @@ Serializing / Deserializing is as simple as calling `to_string` / `from_str`.
 !*/
 
 pub mod de;
-pub mod extensions;
 pub mod ser;
+
+pub mod error;
 pub mod value;
-pub use crate::value::Value;
+
+pub mod extensions;
+
+pub use de::{from_str, Deserializer};
+pub use error::{Error, Result};
+pub use ser::{to_string, Serializer};
+pub use value::{Map, Number, Value};
 
 mod parse;
diff --git a/src/parse.rs b/src/parse.rs
index 95948364..69f0ed66 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -4,7 +4,7 @@ use std::{
     str::{from_utf8, from_utf8_unchecked, FromStr},
 };
 
-use crate::de::{Error, ParseError, Result};
+use crate::error::{Error, ErrorCode, Result};
 use crate::extensions::Extensions;
 
 const DIGITS: &[u8] = b"0123456789ABCDEFabcdef_";
@@ -105,13 +105,13 @@ impl<'a> Bytes<'a> {
         let num_bytes = self.next_bytes_contained_in(DIGITS);
 
         if num_bytes == 0 {
-            return self.err(ParseError::ExpectedInteger);
+            return self.err(ErrorCode::ExpectedInteger);
         }
 
         let s = unsafe { from_utf8_unchecked(&self.bytes[0..num_bytes]) };
 
         if s.as_bytes()[0] == b'_' {
-            return self.err(ParseError::UnderscoreAtBeginning);
+            return self.err(ErrorCode::UnderscoreAtBeginning);
         }
 
         fn calc_num<T: Num>(
@@ -128,17 +128,17 @@ impl<'a> Bytes<'a> {
                 }
 
                 if num_acc.checked_mul_ext(base) {
-                    return bytes.err(ParseError::IntegerOutOfBounds);
+                    return bytes.err(ErrorCode::IntegerOutOfBounds);
                 }
 
                 let digit = bytes.decode_hex(byte)?;
 
                 if digit >= base {
-                    return bytes.err(ParseError::ExpectedInteger);
+                    return bytes.err(ErrorCode::ExpectedInteger);
                 }
 
                 if f(&mut num_acc, digit) {
-                    return bytes.err(ParseError::IntegerOutOfBounds);
+                    return bytes.err(ErrorCode::IntegerOutOfBounds);
                 }
             }
 
@@ -245,7 +245,7 @@ impl<'a> Bytes<'a> {
         } else if self.consume("false") {
             Ok(false)
         } else {
-            self.err(ParseError::ExpectedBoolean)
+            self.err(ErrorCode::ExpectedBoolean)
         }
     }
 
@@ -255,7 +255,7 @@ impl<'a> Bytes<'a> {
 
     pub fn char(&mut self) -> Result<char> {
         if !self.consume("'") {
-            return self.err(ParseError::ExpectedChar);
+            return self.err(ErrorCode::ExpectedChar);
         }
 
         let c = self.peek_or_eof()?;
@@ -272,15 +272,15 @@ impl<'a> Bytes<'a> {
             let pos: usize = self.bytes[..max]
                 .iter()
                 .position(|&x| x == b'\'')
-                .ok_or_else(|| self.error(ParseError::ExpectedChar))?;
+                .ok_or_else(|| self.error(ErrorCode::ExpectedChar))?;
             let s = from_utf8(&self.bytes[0..pos]).map_err(|e| self.error(e.into()))?;
             let mut chars = s.chars();
 
             let first = chars
                 .next()
-                .ok_or_else(|| self.error(ParseError::ExpectedChar))?;
+                .ok_or_else(|| self.error(ErrorCode::ExpectedChar))?;
             if chars.next().is_some() {
-                return self.err(ParseError::ExpectedChar);
+                return self.err(ErrorCode::ExpectedChar);
             }
 
             let _ = self.advance(pos);
@@ -289,7 +289,7 @@ impl<'a> Bytes<'a> {
         };
 
         if !self.consume("'") {
-            return self.err(ParseError::ExpectedChar);
+            return self.err(ErrorCode::ExpectedChar);
         }
 
         Ok(c)
@@ -375,21 +375,21 @@ impl<'a> Bytes<'a> {
         Ok(peek)
     }
 
-    pub fn err<T>(&self, kind: ParseError) -> Result<T> {
+    pub fn err<T>(&self, kind: ErrorCode) -> Result<T> {
         Err(self.error(kind))
     }
 
-    pub fn error(&self, kind: ParseError) -> Error {
-        Error::Parser(
-            kind,
-            Position {
+    pub fn error(&self, kind: ErrorCode) -> Error {
+        Error {
+            code: kind,
+            position: Position {
                 line: self.line,
                 col: self.column,
             },
-        )
+        }
     }
 
-    pub fn expect_byte(&mut self, byte: u8, error: ParseError) -> Result<()> {
+    pub fn expect_byte(&mut self, byte: u8, error: ErrorCode) -> Result<()> {
         self.eat_byte()
             .and_then(|b| if b == byte { Ok(()) } else { self.err(error) })
     }
@@ -401,7 +401,7 @@ impl<'a> Bytes<'a> {
         }
 
         if !self.consume_all(&["#", "!", "[", "enable", "("])? {
-            return self.err(ParseError::ExpectedAttribute);
+            return self.err(ErrorCode::ExpectedAttribute);
         }
 
         self.skip_ws()?;
@@ -410,7 +410,7 @@ impl<'a> Bytes<'a> {
         loop {
             let ident = self.identifier()?;
             let extension = Extensions::from_ident(ident).ok_or_else(|| {
-                self.error(ParseError::NoSuchExtension(
+                self.error(ErrorCode::NoSuchExtension(
                     from_utf8(ident).unwrap().to_owned(),
                 ))
             })?;
@@ -421,7 +421,7 @@ impl<'a> Bytes<'a> {
 
             // If we have no comma but another item, return an error
             if !comma && self.check_ident_char(0) {
-                return self.err(ParseError::ExpectedComma);
+                return self.err(ErrorCode::ExpectedComma);
             }
 
             // If there's no comma, assume the list ended.
@@ -437,7 +437,7 @@ impl<'a> Bytes<'a> {
         if self.consume_all(&[")", "]"])? {
             Ok(extensions)
         } else {
-            Err(self.error(ParseError::ExpectedAttributeEnd))
+            Err(self.error(ErrorCode::ExpectedAttributeEnd))
         }
     }
 
@@ -454,7 +454,7 @@ impl<'a> Bytes<'a> {
         let num_bytes = self.next_bytes_contained_in(FLOAT_CHARS);
 
         let s = unsafe { from_utf8_unchecked(&self.bytes[0..num_bytes]) };
-        let res = FromStr::from_str(s).map_err(|_| self.error(ParseError::ExpectedFloat));
+        let res = FromStr::from_str(s).map_err(|_| self.error(ErrorCode::ExpectedFloat));
 
         let _ = self.advance(num_bytes);
 
@@ -478,9 +478,9 @@ impl<'a> Bytes<'a> {
                 let second = self
                     .bytes
                     .get(1)
-                    .ok_or_else(|| self.error(ParseError::Eof))?;
+                    .ok_or_else(|| self.error(ErrorCode::Eof))?;
                 if *second == b'"' || *second == b'#' {
-                    return self.err(ParseError::ExpectedIdentifier);
+                    return self.err(ErrorCode::ExpectedIdentifier);
                 }
             }
 
@@ -488,7 +488,7 @@ impl<'a> Bytes<'a> {
 
             Ok(bytes)
         } else {
-            self.err(ParseError::ExpectedIdentifier)
+            self.err(ErrorCode::ExpectedIdentifier)
         }
     }
 
@@ -543,7 +543,7 @@ impl<'a> Bytes<'a> {
         self.bytes
             .get(0)
             .cloned()
-            .ok_or_else(|| self.error(ParseError::Eof))
+            .ok_or_else(|| self.error(ErrorCode::Eof))
     }
 
     pub fn signed_integer<T>(&mut self) -> Result<T>
@@ -571,7 +571,7 @@ impl<'a> Bytes<'a> {
         } else if self.consume("r") {
             self.raw_string()
         } else {
-            self.err(ParseError::ExpectedString)
+            self.err(ErrorCode::ExpectedString)
         }
     }
 
@@ -583,7 +583,7 @@ impl<'a> Bytes<'a> {
             .iter()
             .enumerate()
             .find(|&(_, &b)| b == b'\\' || b == b'"')
-            .ok_or_else(|| self.error(ParseError::ExpectedStringEnd))?;
+            .ok_or_else(|| self.error(ErrorCode::ExpectedStringEnd))?;
 
         if *end_or_escape == b'"' {
             let s = from_utf8(&self.bytes[..i]).map_err(|e| self.error(e.into()))?;
@@ -614,7 +614,7 @@ impl<'a> Bytes<'a> {
                     .iter()
                     .enumerate()
                     .find(|&(_, &b)| b == b'\\' || b == b'"')
-                    .ok_or(ParseError::Eof)
+                    .ok_or(ErrorCode::Eof)
                     .map_err(|e| self.error(e))?;
 
                 i = new_i;
@@ -636,7 +636,7 @@ impl<'a> Bytes<'a> {
         let _ = self.advance(num_hashes);
 
         if !self.consume("\"") {
-            return self.err(ParseError::ExpectedString);
+            return self.err(ErrorCode::ExpectedString);
         }
 
         let ending = [&[b'"'], hashes].concat();
@@ -644,7 +644,7 @@ impl<'a> Bytes<'a> {
             .bytes
             .windows(num_hashes + 1)
             .position(|window| window == ending.as_slice())
-            .ok_or_else(|| self.error(ParseError::ExpectedStringEnd))?;
+            .ok_or_else(|| self.error(ErrorCode::ExpectedStringEnd))?;
 
         let s = from_utf8(&self.bytes[..i]).map_err(|e| self.error(e.into()))?;
 
@@ -683,7 +683,7 @@ impl<'a> Bytes<'a> {
             c @ b'0'..=b'9' => Ok(c - b'0'),
             c @ b'a'..=b'f' => Ok(10 + c - b'a'),
             c @ b'A'..=b'F' => Ok(10 + c - b'A'),
-            _ => self.err(ParseError::InvalidEscape("Non-hex digit found")),
+            _ => self.err(ErrorCode::InvalidEscape("Non-hex digit found")),
         }
     }
 
@@ -697,7 +697,7 @@ impl<'a> Bytes<'a> {
             b't' => '\t',
             b'x' => self.decode_ascii_escape()? as char,
             b'u' => {
-                self.expect_byte(b'{', ParseError::InvalidEscape("Missing {"))?;
+                self.expect_byte(b'{', ErrorCode::InvalidEscape("Missing {"))?;
 
                 let mut bytes: u32 = 0;
                 let mut num_digits = 0;
@@ -719,17 +719,17 @@ impl<'a> Bytes<'a> {
                 }
 
                 if num_digits == 0 {
-                    return self.err(ParseError::InvalidEscape(
+                    return self.err(ErrorCode::InvalidEscape(
                         "Expected 1-6 digits, got 0 digits",
                     ));
                 }
 
-                self.expect_byte(b'}', ParseError::InvalidEscape("No } at the end"))?;
+                self.expect_byte(b'}', ErrorCode::InvalidEscape("No } at the end"))?;
                 char_from_u32(bytes)
-                    .ok_or_else(|| self.error(ParseError::InvalidEscape("Not a valid char")))?
+                    .ok_or_else(|| self.error(ErrorCode::InvalidEscape("Not a valid char")))?
             }
             _ => {
-                return self.err(ParseError::InvalidEscape("Unknown escape character"));
+                return self.err(ErrorCode::InvalidEscape("Unknown escape character"));
             }
         };
 
@@ -755,7 +755,7 @@ impl<'a> Bytes<'a> {
                             .count();
 
                         if self.bytes.is_empty() {
-                            return self.err(ParseError::UnclosedBlockComment);
+                            return self.err(ErrorCode::UnclosedBlockComment);
                         }
 
                         let _ = self.advance(bytes);
@@ -767,11 +767,11 @@ impl<'a> Bytes<'a> {
                             level -= 1;
                         } else {
                             self.eat_byte()
-                                .map_err(|_| self.error(ParseError::UnclosedBlockComment))?;
+                                .map_err(|_| self.error(ErrorCode::UnclosedBlockComment))?;
                         }
                     }
                 }
-                b => return self.err(ParseError::UnexpectedByte(b as char)),
+                b => return self.err(ErrorCode::UnexpectedByte(b as char)),
             }
 
             Ok(true)
diff --git a/src/ser/mod.rs b/src/ser/mod.rs
index 3bddba57..de180072 100644
--- a/src/ser/mod.rs
+++ b/src/ser/mod.rs
@@ -1,9 +1,7 @@
 use serde::{ser, Deserialize, Serialize};
-use std::{
-    error::Error as StdError,
-    fmt::{Display, Formatter, Result as FmtResult, Write},
-};
+use std::fmt::Write;
 
+use crate::error::{Error, Result};
 use crate::extensions::Extensions;
 
 mod value;
@@ -31,38 +29,6 @@ where
     Ok(s.output)
 }
 
-/// Serialization result.
-pub type Result<T> = std::result::Result<T, Error>;
-
-/// Serialization error.
-#[derive(Clone, Debug, PartialEq)]
-pub enum Error {
-    /// A custom error emitted by a serialized value.
-    Message(String),
-}
-
-impl Display for Error {
-    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
-        match *self {
-            Error::Message(ref e) => write!(f, "Custom message: {}", e),
-        }
-    }
-}
-
-impl ser::Error for Error {
-    fn custom<T: Display>(msg: T) -> Self {
-        Error::Message(msg.to_string())
-    }
-}
-
-impl StdError for Error {
-    fn description(&self) -> &str {
-        match *self {
-            Error::Message(ref e) => e,
-        }
-    }
-}
-
 /// Pretty serializer state
 struct Pretty {
     indent: usize,


Test patch:
diff --git a/src/de/tests.rs b/src/de/tests.rs
index de302849..f74b87b6 100644
--- a/src/de/tests.rs
+++ b/src/de/tests.rs
@@ -149,13 +149,16 @@ y: 2.0 // 2!
     );
 }
 
-fn err<T>(kind: ParseError, line: usize, col: usize) -> Result<T> {
-    Err(Error::Parser(kind, Position { line, col }))
+fn err<T>(kind: ErrorCode, line: usize, col: usize) -> Result<T> {
+    Err(Error {
+        code: kind,
+        position: Position { line, col },
+    })
 }
 
 #[test]
 fn test_err_wrong_value() {
-    use self::ParseError::*;
+    use self::ErrorCode::*;
     use std::collections::HashMap;
 
     assert_eq!(from_str::<f32>("'c'"), err(ExpectedFloat, 1, 1));
@@ -204,7 +207,10 @@ fn forgot_apostrophes() {
     let de: Result<(i32, String)> = from_str("(4, \"Hello)");
 
     assert!(match de {
-        Err(Error::Parser(ParseError::ExpectedStringEnd, _)) => true,
+        Err(Error {
+            code: ErrorCode::ExpectedStringEnd,
+            position: _,
+        }) => true,
         _ => false,
     });
 }
@@ -213,14 +219,14 @@ fn forgot_apostrophes() {
 fn expected_attribute() {
     let de: Result<String> = from_str("#\"Hello\"");
 
-    assert_eq!(de, err(ParseError::ExpectedAttribute, 1, 2));
+    assert_eq!(de, err(ErrorCode::ExpectedAttribute, 1, 2));
 }
 
 #[test]
 fn expected_attribute_end() {
     let de: Result<String> = from_str("#![enable(unwrap_newtypes) \"Hello\"");
 
-    assert_eq!(de, err(ParseError::ExpectedAttributeEnd, 1, 28));
+    assert_eq!(de, err(ErrorCode::ExpectedAttributeEnd, 1, 28));
 }
 
 #[test]
@@ -229,7 +235,7 @@ fn invalid_attribute() {
 
     assert_eq!(
         de,
-        err(ParseError::NoSuchExtension("invalid".to_string()), 1, 18)
+        err(ErrorCode::NoSuchExtension("invalid".to_string()), 1, 18)
     );
 }
 
diff --git a/tests/comments.rs b/tests/comments.rs
index d49ef73c..f5d6f73c 100644
--- a/tests/comments.rs
+++ b/tests/comments.rs
@@ -1,4 +1,4 @@
-use ron::de::{from_str, Error as RonErr, ParseError, Position};
+use ron::de::{from_str, Error as RonErr, ErrorCode, Position};
 
 #[test]
 fn test_simple() {
@@ -44,9 +44,9 @@ fn test_unclosed() {
 \"THE VALUE (which is invalid)\"
 "
         ),
-        Err(RonErr::Parser(
-            ParseError::UnclosedBlockComment,
-            Position { col: 1, line: 9 }
-        ))
+        Err(RonErr {
+            code: ErrorCode::UnclosedBlockComment,
+            position: Position { col: 1, line: 9 }
+        })
     );
 }
diff --git a/tests/preserve_sequence.rs b/tests/preserve_sequence.rs
index 0b040e83..02cf8d81 100644
--- a/tests/preserve_sequence.rs
+++ b/tests/preserve_sequence.rs
@@ -20,7 +20,7 @@ struct Nested {
 }
 
 fn read_original(source: &str) -> String {
-    source.to_string()
+    source.to_string().replace("\r\n", "\n")
 }
 
 fn make_roundtrip(source: &str) -> String {
@@ -34,7 +34,8 @@ fn make_roundtrip(source: &str) -> String {
     let pretty = PrettyConfig::new()
         .with_depth_limit(3)
         .with_separate_tuple_members(true)
-        .with_enumerate_arrays(true);
+        .with_enumerate_arrays(true)
+        .with_new_line("\n".into());
     to_string_pretty(&config, pretty).expect("Serialization failed")
 }
 
diff --git a/tests/value.rs b/tests/value.rs
index 05b155ff..3e7ea8f0 100644
--- a/tests/value.rs
+++ b/tests/value.rs
@@ -67,14 +67,17 @@ fn seq() {
 
 #[test]
 fn unit() {
-    use ron::de::{Error, ParseError, Position};
+    use ron::error::{Error, ErrorCode, Position};
 
     assert_eq!("()".parse(), Ok(Value::Unit));
     assert_eq!("Foo".parse(), Ok(Value::Unit));
 
     assert_eq!(
         "".parse::<Value>(),
-        Err(Error::Parser(ParseError::Eof, Position { col: 1, line: 1 }))
+        Err(Error {
+            code: ErrorCode::Eof,
+            position: Position { col: 1, line: 1 }
+        })
     );
 }