Coder Social home page Coder Social logo

Comments (44)

sunli829 avatar sunli829 commented on April 28, 2024 1

I added the Any::parse_value method in version 1.10.11.

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024 1

that looks good, similar to the containers used in Actix for json and data. Thanks!

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024 1

Thank you. I am writing a new parser and will help you later. 😁

from async-graphql.

nicolaiunrein avatar nicolaiunrein commented on April 28, 2024 1

I'm not at a computer right now but will update to the latest version on monday and check things.

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024 1

@sunli829 it worked! thanks

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024 1

yea

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

I have figured it out: #[InputObject]

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

Yep, no luck with this so far. Any help would be appreciated and for sure will be useful for anyone else trying to implement a similar thing.

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

I'm going to go for Scalar implementation for this one as it seems like the only way forward at the moment. My guess is that #[InputObject] does not like complex types and expects a set of primitives.

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

Maybe you can use Any type.

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

Because the GraphQL's type system doesn't have a Map type.

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

I'm going to go for Scalar implementation for this one as it seems like the only way forward at the moment. My guess is that #[InputObject] does not like complex types and expects a set of primitives.

Yes, if you want the input value to be a Map, that's the only way, and Any scalar is to handle that.

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

Well, I basically want to receive a json there and then have it converted to the Criteria object, but without extra steps like calling a method. Any idea how that could be done?

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

Actually, nevermind.

Here's my solution to this:

pub type JsonMap = serde_json::Map<String, serde_json::Value>;

#[derive(Clone)]
pub struct Map {
    pub data: JsonMap,
}

#[Scalar]
impl ScalarType for Map {
    fn type_name() -> &'static str {
        "Map"
    }

    fn parse(value: &Value) -> Option<Self> {
        match value {
            Value::String(s) => match serde_json::from_str(s) {
                Ok(map) => Some(Map { data: map }),
                _ => None,
            },
            _ => None,
        }
    }

    fn to_json(&self) -> Result<serde_json::Value> {
        Ok(self.data.clone().into())
    }
}

And I'm going to use a similar one to define criteria scalars

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

You can call ScalarType::to_json on Any and parse the result into Criteria using serde_json::from_value, I will provide a convenient way to do this later.

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

Actually, nevermind.

Here's my solution to this:

pub type JsonMap = serde_json::Map<String, serde_json::Value>;

#[derive(Clone)]
pub struct Map {
    pub data: JsonMap,
}

#[Scalar]
impl ScalarType for Map {
    fn type_name() -> &'static str {
        "Map"
    }

    fn parse(value: &Value) -> Option<Self> {
        match value {
            Value::String(s) => match serde_json::from_str(s) {
                Ok(map) => Some(Map { data: map }),
                _ => None,
            },
            _ => None,
        }
    }

    fn to_json(&self) -> Result<serde_json::Value> {
        Ok(self.data.clone().into())
    }
}

And I'm going to use a similar one to define criteria scalars

Yes, that's the way to do it.😁

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

If you wait for me for 5 minutes, I will add a method to Any, which will make it easier for you. I don't think it is necessary to add a Map type.

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

@sunli829 sure, but then the method definition is going to turn from async fn records(&self, criteria: Criteria, start: i64, skip: i64, order: SortCriteria)
to
async fn records(&self, criteria: Any, start: i64, skip: i64, order: Any)
Correct? And that would mean I'd need to manually convert those each time to whatever type I need.

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

Yes, you have to do it manually every time, because GraphQL doesn't have a Map type. Unless I extend the GraphQL, which leads to other compatibility problems, I don't think it's worth the trouble.😁

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

@sunli829 great, I'll look into it! I'm curious why fn gql_value_to_json_value(value: &Value) -> serde_json::Value { is not exposed, that would make implementing types like Criteria via Scalar a matter of simple derive macro possibly.

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

You can call Any::to_json directly, so there's no need to expose it.

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

@sunli829 yeah, but that only works with an instance of Any as it's not a static method, so I'd have to reimplement this for any other type should the need arise. But I think I have a good idea how to use 'Any' to my advantage now. Thanks!

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

I guess I can implement a CriteriaInput object, that would contain 'Any' and have it convert using a From/ Into to whatever I need.

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

@sunli829 is there any way currently to support something like that

#[serde(transparent)]
pub struct CriteriaInput {
    pub data: Any
}

So that I could have a custom input object, but it would actually resolve to Any for graphql instead of {"data": Any...}.

Because then I can implement my helper From/Into conversions for CriteriaInput and then my whole use-case would be more eloquent than doing something like:
let criteria: Criteria = any_val.parse_value which is going to be quite repetitive.

I guess you know what "transparent" does, it just basically deserilizes things into data directly.

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

You've given me some inspiration, and I'm going to add a Json scalar type.😁

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

@ruseinov Look at this Json scalar, I think it's a little easier to use.

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

looking into it

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

@sunli829 One think that'd be nice to have is Clone.

#[derive(Clone)]
pub struct RecordTypeInput {
    pub name: String,
    pub schema: Json<JsonMap>,
}

is not possible now

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

It does not block me, however might be a nice to have. I leave it up to you to see if it makes sense.

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

I have found another curious issue, however:
This works fine

    async fn create_record_type(
        &self,
        ctx: &Context<'_>,
        record_type: crate::model::RecordTypeInput,
    ) 

With that input:

mutation createRecordType ($recordType: RecordTypeInput) {
    createRecordType (recordType: $recordType) {
...
        
    }
}

where variables are:

{
  "recordType": {
    "name": "test",
    "schema": {
    "$schema": "http://json-schema.org/draft-07/schema",
    "$id": "http://example.com/example.json",
    "type": "object",
    "title": "The Root Schema",
    "description": "The root schema comprises the entire JSON document.",
    "default": {},
    "additionalProperties": true,
    "required": [
        "id"
    ],
    "properties": {
        "id": {
            "$id": "#/properties/id",
            "type": "integer",
            "title": "The Id Schema",
            "description": "An explanation about the purpose of this instance.",
            "default": "",
            "examples": [
                1
            ]
        }
    }
}
  }
}

So the schema is plain JSON.

However, when I'm trying to use a JSON type directly like this:

    async fn records(
        &self,
        criteria: Json<Criteria>,
        start: i64,
        skip: i64,
        order: Json<SortCriteria>,
    ) -> FieldResult<Vec<Record>> {
        info!("{:?}", criteria.0);
        info!("{:?}", order.0);
        Ok(Vec::new())
    }
query records ($criteria: JSON, $start: Int, $skip: Int, $order: JSON) {
    records (criteria: $criteria, start: $start, skip: $skip, order: $order) {
        id
        data
        createdAt
        updatedAt
    }
}

with

{
  "criteria": {
	"name": {"=": 1}
   },
  "start": 0,
  "skip": 0,
  "order": {}
}

yields

{
    "errors": [
        {
            "message": "Expected type \"JSON!\", found {name: {=: 1}}.",
            "locations": [
                {
                    "line": 2,
                    "column": 5
                }
            ]
        }
    ]
}

However it works if I pass an empty object to criteria :/

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

@ruseinov Can you provide a minimum test case that can be run?😁

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

I'll do that, but basically if you try using the Json scalar to wrap a struct and use that as a parameter in, say, a query endpoint - that only works with an empty object passed to that parameter. Otherwise there is an issue. I'll try to reproduce it as an isolated test case

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

Because I tried, but I couldn't reproduce the problem. 😣

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

I can give you what I have now, but I'll def dig into it more and get you a test:

#[Object]
impl QueryRoot {
    async fn records(
        &self,
        criteria: Json<Criteria>,
        start: i64,
        skip: i64,
        order: Json<SortCriteria>,
    ) -> FieldResult<Vec<Record>> {
        info!("{:?}", criteria.0);
        info!("{:?}", order.0);
        Ok(Vec::new())
    }
}

// you can use String, String map for simplicity, it does not seem to matter
pub struct Criteria {
    pub value: HashMap<String, Vec<Criterion>>,
}

and the test code:

curl --location --request POST 'localhost:8089/graphql' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"query records ($criteria: JSON, $start: Int, $skip: Int, $order: JSON) {\n    records (criteria: $criteria, start: $start, skip: $skip, order: $order) {\n        id\n        data\n        createdAt\n        updatedAt\n    }\n}","variables":{"criteria":{"name":{"=":1}},"start":0,"skip":0,"order":{}}}'

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

@ruseinov How is Criterion defined?

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

#[derive(Serialize, Deserialize, Debug)]
pub struct Criterion {
    pub value: Value,
    pub operator: Operator,
}

#[derive(Deserialize, Debug)]
pub enum Operator {
    Equal,
    NotEqual,
    LessThan,
    GreaterThan,
    Like,
    IncorrectValue,
}

impl From<&str> for Operator {
    fn from(str: &str) -> Self {
        match str {
            ">" => GreaterThan,
            "<" => LessThan,
            "=" => Equal,
            "!=" => NotEqual,
            "like" => Like,
            _ => IncorrectValue,
        }
    }
}

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

    fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
        formatter.write_str("An operator, e.g. '>', '<', '=', '!=', 'like'")
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        let op = Operator::from(v);
        match op {
            IncorrectValue => Err(de::Error::invalid_value(Unexpected::Str(v), &self)),
            _ => Ok(op),
        }
    }

    fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        self.visit_str(v)
    }

    fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        self.visit_str(v.as_str())
    }
}

from async-graphql.

ruseinov avatar ruseinov commented on April 28, 2024

Where Value is serde_json::Value

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

@ruseinov

Your input Json is incorrect, I am going to change the return value of ScalarType::parse to Result, so you can see the correct error message.

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

I created a new issue to track this. #70

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

@ruseinov Please upgrade to version 1.11.1 to see the real error message. 😁

from async-graphql.

nicolaiunrein avatar nicolaiunrein commented on April 28, 2024

You can call Any::to_json directly, so there's no need to expose it.

Once the parser and the Any type is under our control we can add conversion to and from serde_json::Value.

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

@nicolaiunrein Did you also use the Any type? Now it can be replaced with Json, which is more convenient.

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

@nicolaiunrein Then help to see if this code is still needed?😁

impl Any {
    /// Parse this `Any` value to T by `serde_json`.
    pub fn parse_value<T: DeserializeOwned>(&self) -> std::result::Result<T, serde_json::Error> {
        serde_json::from_value(self.to_json().unwrap())
    }
}

impl<T> From<T> for Any
where
    T: Into<Value>,
{
    fn from(value: T) -> Any {
        Any(value.into())
    }
}

from async-graphql.

sunli829 avatar sunli829 commented on April 28, 2024

@ruseinov Is the input json format incorrect?

from async-graphql.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.