Skip to content

Commit

Permalink
Introduce type_ hack (#396)
Browse files Browse the repository at this point in the history
workaround: Accept 'type' as valid key in object by appending underscore, bump version to 0.4.4
GitOrigin-RevId: e051a3684d7d382d5d8897e2fd7b8bf34eab8c7d
  • Loading branch information
llogiq authored and oq-bot committed May 7, 2021
1 parent 3783ef4 commit 26d0705
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 102 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "synth-core"
version = "0.4.3"
version = "0.4.4"
authors = [
"Damien Broka <[email protected]>",
"Christos Hadjiaslanis <[email protected]>"
Expand Down
117 changes: 63 additions & 54 deletions core/src/schema/content/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,75 +371,80 @@ pub mod tests {
"type": "object",
"user_id": {
"type": "number",
"subtype": "u64",
"range": {
"low": 0,
"high": 1_000_000,
"step": 1
}
"subtype": "u64",
"range": {
"low": 0,
"high": 1_000_000,
"step": 1
}
},
"type_": { // checks the underscore hack
"type": "string",
"pattern": "user|contributor|maintainer"
},
"first_name": {
"type": "string",
"faker": {
"generator": "first_name"
}
"type": "string",
"faker": {
"generator": "first_name"
}
},
"last_name": {
"type": "string",
"faker": {
"generator": "last_name"
}
"type": "string",
"faker": {
"generator": "last_name"
}
},
"address": {
"type": "object",
"postcode": {
"type": "string",
"pattern": "[A-Z]{1}[a-z]{3,6}"
},
"country": {
"optional": true,
"type": "string",
"faker": {
"generator": "country_code",
"representation": "alpha-2"
}
},
"numbers": {
"type": "number",
"subtype": "u64",
"range": {
"low": 0,
"high": 1_000_000,
"step": 1
}
}
"type": "object",
"postcode": {
"type": "string",
"pattern": "[A-Z]{1}[a-z]{3,6}"
},
"country": {
"optional": true,
"type": "string",
"faker": {
"generator": "country_code",
"representation": "alpha-2"
}
},
"numbers": {
"type": "number",
"subtype": "u64",
"range": {
"low": 0,
"high": 1_000_000,
"step": 1
}
}
},
"friends": {
"type": "array",
"length": {
"type": "number",
"subtype": "u64",
"constant": 100
},
"content": {
"type": "one_of",
"variants": [ {
"type": "string",
"pattern": "[A-Z]{1}[a-z]{3,6}"
}, {
"type": "number",
"subtype": "f64",
"range": {
"type": "array",
"length": {
"type": "number",
"subtype": "u64",
"constant": 100
},
"content": {
"type": "one_of",
"variants": [ {
"type": "string",
"pattern": "[A-Z]{1}[a-z]{3,6}"
}, {
"type": "number",
"subtype": "f64",
"range": {
"low": -75.2,
"high": -11,
"step": 0.1
}
} ]
}
}
} ]
}
}
});
static ref USER: serde_json::Value = json!({
"user_id" : 123,
"type": "user",
"first_name" : "John",
"last_name": "Smith",
"address" : {
Expand All @@ -452,13 +457,15 @@ pub mod tests {

#[test]
fn user_schema_accepts() {
println!("{:#?}", *USER_SCHEMA);
USER_SCHEMA.accepts(&USER).unwrap()
}

#[test]
fn user_schema_declined_extra_field() {
let user = json!({
"user_id" : 123,
"type" : "contributor",
"first_name" : "John",
"last_name": "Smith",
"address" : {
Expand All @@ -476,6 +483,7 @@ pub mod tests {
fn user_schema_declined_missing_field() {
let user = json!({
"user_id" : 123,
"type" : "maintainer",
"first_name" : "John",
"last_name": "Smith",
"address" : {
Expand All @@ -492,6 +500,7 @@ pub mod tests {
fn user_schema_declined_bad_array() {
let user = json!({
"user_id" : 123,
"type" : "user",
"first_name" : "John",
"last_name": "Smith",
"address" : {
Expand Down
59 changes: 57 additions & 2 deletions core/src/schema/content/object.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,68 @@
use super::prelude::*;
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::fmt;
use std::ops::Not;
use serde::{ser::{Serialize, Serializer, SerializeMap}, de::{Deserialize, Deserializer, MapAccess}};

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub struct ObjectContent {
#[serde(flatten)]
pub fields: BTreeMap<String, FieldContent>,
}

fn add_type_underscore(key: &str) -> Cow<str> {
if key.starts_with("type") && key[4..].bytes().all(|b| b == b'_') {
Cow::Owned(key.to_string() + "_")
} else {
Cow::Borrowed(key)
}
}

fn remove_type_underscore(mut key: String) -> String {
if key.starts_with("type_") && key[5..].bytes().all(|b| b == b'_') {
key.truncate(key.len() - 1);
}
key
}

impl Serialize for ObjectContent {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut map = serializer.serialize_map(Some(self.fields.len()))?;
for (k, v) in &self.fields {
map.serialize_entry(&add_type_underscore(k), v)?;
}
map.end()
}
}

struct ObjectContentVisitor;

impl<'de> Visitor<'de> for ObjectContentVisitor {
type Value = ObjectContent;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "an object's contents")
}

fn visit_map<M: MapAccess<'de>>(self, mut access: M) -> Result<Self::Value, M::Error> {
let mut fields = BTreeMap::new();
while let Some((key, value)) = access.next_entry()? {
let key: String = key;
if fields.contains_key(&key) {
return Err(serde::de::Error::custom(format!("duplicate field: {}", &key)));
}
fields.insert(remove_type_underscore(key), value);
}
Ok(ObjectContent { fields })
}
}

impl<'de> Deserialize<'de> for ObjectContent {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_map(ObjectContentVisitor)
}
}

impl ObjectContent {
pub fn get(&self, field: &str) -> Result<&FieldContent> {
let suggest = suggest_closest(self.fields.keys(), field).unwrap_or_default();
Expand Down
2 changes: 1 addition & 1 deletion default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
, release ? true
}:
let
version = "0.4.3";
version = "0.4.4";
darwinBuildInputs =
stdenv.lib.optionals stdenv.hostPlatform.isDarwin (with darwin.apple_sdk.frameworks; [
libiconv
Expand Down
22 changes: 21 additions & 1 deletion docs/docs/content/object.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,24 @@ Values of objects can be made *optional* by specifying the `"optional": true` at
}
}
}
```
```

If a field should have the name `"type"`, this would clash with the predefined object attribute of the same name.
This can be worked around by changing the name to `"type_"`. The additional underscore will be removed in the
generated values.

#### Example

```json synth
{
"type": "object",
"type_": {
"type": "string",
"categorical": {
"user": 90,
"contributor": 8,
"admin": 2
}
}
}
```
2 changes: 1 addition & 1 deletion synth/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "synth"
version = "0.4.3"
version = "0.4.4"
authors = [
"Damien Broka <[email protected]>",
"Christos Hadjiaslanis <[email protected]>"
Expand Down
Loading

0 comments on commit 26d0705

Please sign in to comment.