pa-bru / graphql-cost-analysis Goto Github PK
View Code? Open in Web Editor NEWA Graphql query cost analyzer.
License: MIT License
A Graphql query cost analyzer.
License: MIT License
Hey! I'm looking for graphql api protection tools and this looks neat but I'm worried about using unmaintained dep in my app.
Using this with the same way spectrum does:
class ProtectedApolloServer extends ApolloServer {
async createGraphQLServerOptions(req, res) {
const options = await super.createGraphQLServerOptions(req, res)
return {
...options,
validationRules: [
...options.validationRules,
costAnalysis({
maximumCost: 10,
defaultCost: 1,
variables: req.body.variables,
createError: (max, actual) => {
const err = new ForbiddenError(
`GraphQL query exceeds maximum complexity, please remove some nesting or fields and try again. (max: ${max}, actual: ${actual})`
)
return err
},
}),
],
}
}
}
But when I add @cost(multipliers: ["first", "last"], complexity: 5)
to my types I get the error Error: Unknown directive "cost".
.
Also using typescript and ziet/ncc
to compile my backend.
Any thoughts?
are there any plans to switch from flow to typescript? i like your tool but its one of a few dependencies that neither has @types/
-package nor has direct typescript-declaration in shipped npmjs.com-artifact. that prevents me from doing fully strict typescript-type-checking, cause such dependencies get implictAny type 😬
I've been looking at the documentation, and this seems like a decent tool for our use case.
However, we have certain complexity characteristics, that I'm not sure if we can calculate correctly. Please advise if these scenarios are supported:
We're using apollo server as a gateway, that resolves all queries by making API calls to a back-end. For some types, there are some fields that are always included with the resource (basically zero cost), and there are other fields that require an additional round-trip to the back-end. If one or more of these fields are included, we need to make that extra round-trip, so basically any additional field will have zero cost:
type User {
# "zero-cost" fields
id: String!
name: String!
email: String!
# one or more of these fields will add a cost, but if we include one,
# it doesn't matter if we include all of them
address: String!
phone: String!
birthDate: String!
}
We want a significant complexity value to be added, if any of the fields address
, phone
or birthDate
are included in the query, but we don't want it to add up, if several of these fields are included.
Another scenario is like this:
type Query {
allUsers: [User!]!
}
type User {
id: String!
messages: [Message!]!
}
type Message {
# always included
id: String!
text: String!
# extra round-trip needed
seenBy: [User!]!
}
In this scenario, we have a back-end API method that returns all users. We have another API method that returns all messages to all users. If we have a query like this:
{ allUsers {
id
messages { id text }
}
We only need to make two API calls. So the cost of including the messages
doesn't depend on the number of users. This seems like a use-case for setting useMultipliers
to false
.
However, if we include the seenBy
field, it will trigger an extra API call for every message, so in that case, we do want multipliers for the number of users and messages to apply. Is this scenario possible with this library?
Why is complexity
limited to 10? I could have an outlier query that has a complexity of 100. Why doesn't graphql-cost-analysis
leave the complexity numbers to me?
Is it possible to pass the cost to the resolver, or to create a new type (for example, to take query{remaining {cost, remainingCost}})?
Specifying both a multiplier and having to set useMultipliers
to true seems redundant—surely if I set a multiplier I want to use it:
type Whatever {
# 🤔
resolver(first: Int) Something @cost(multiplier: "first", useMultipliers: true)
}
What is the best way to ask from costAnalysis API what is the cost, without any graphql server instance?
We're resorting currently to this, but would be nice to have easy API to get the cost..
const { validate, parse, ValidationContext, TypeInfo } = require('graphql');
const costAnalysis = require('graphql-cost-analysis').default;
function getCurrentCost(query, schema, variables) {
const queryAST = parse(query);
const validationContext = new ValidationContext(schema, queryAST, new TypeInfo(schema));
const costAnalyser = costAnalysis({ variables, ...costLimits })(validationContext);
validate(schema, queryAST, [() => costAnalyser]);
return costAnalyser.cost;
}
In a typical connection one can paginate both fore-and backwards with the first
or last
arguments, respectively. How can I tell graphql-cost-analysis that both first and last are multipliers?
type Community {
# Get a list of posts in a community
# How can I tell graphql-cost-analysis that both first and last are multipliers?
postConnection(first: Int, after: String, last: Int, before: String): PostConnection
}
According to Docs, Cost multiply by array's length:
N.B: if the parameter is an array, its multiplier value will be the length of the array (cf EG2).
Can we have this for Objects too? For example:
input PagerOptions {
before: String
after: String
page: Int = 1
limit: Int = 10
}
type Query {
sections ( pager: PagerOptions = {} ):
SectionsConnection @cost(multipliers: ["pager.limit", "pager"], complexity: {min: 3})
}
If we do so:
query GetSections {
sections (pager: {
limit: 50, # Count: 1
page: 10, # Count: 2
}) {
nodes {
id
status
}
}
}
Then final counts should be:
50 + 2
(50: limit, 1+1: pager fields) = 52
What you say? Is it worthy?
I don't know if it is a bug or not
For this test case
test('should consider default cost with operationName', done => {
const ast = parse(
`
query operationA {
defaultCost
}
query operationB {
defaultCost
}
`)
const context = new ValidationContext(schema, ast, typeInfo)
const visitor = new CostAnalysis(context, {
maximumCost: 100,
defaultCost: 12,
onComplete: cost => {
console.log('cost', cost)
done()
}
})
visit(ast, visitWithTypeInfo(typeInfo, visitor))
})
I have 2 queries, onComplete
will be called 2 times
First time, cost is 12, second time 24. 🤔
After that, i use operationName
, like "operationName":"operationB"
Should operationA
be analysed and count in the total cost ?
I'm trying to use graphql-cost-analysis. but it just said costAnalysis is not a function
.
I can not show you entire code. but it's like this.
const { graphqlExpress } = require('graphql-server-express');
const costAnalysis = require('graphql-cost-analysis')
...
graphqlExpress(req => {
return {
schema,
rootValue: null,
validationRules: [
costAnalysis({
variables: req.body.variables,
maximumCost: 10
}),
],
}
})
...
and the result of console.log(costAnalysis)
is like this.
{ default: [Function: createCostAnalysis] }
What am I missing?
thanks
Does this support mutations and subscriptions? I do care for mutations.
A feature added to Apollo Server 2.4 (apollographql/apollo-server#2111) introduces a document store that caches successfully parsed and validated documents for future requests (LRU).
This can lead to a case where a query with good variables passes the dynamic validation cost check (see #12) and subsequent requests with the same query but different, larger variables would not trigger the validation rule due to the usage of the cache.
I don't have a sample reproduction repository, but here is an example with maximumCost: 10
:
Schema:
type Query {
"List businesses."
businesses(page: Int! = 1, pageSize: Int! = 10): BusinessConnection
@cost(complexity: 1, multipliers: ["pageSize"])
}
Query:
query ($pageSize: Int! = 10) {
businesses(pageSize: $pageSize) {
edges {
node {
id
name
}
}
}
}
First request query variables (validation is run) - passes validation:
{
"pageSize": 10
}
Second request query variables (validation is skipped) - should fail validation but passes
{
"pageSize": 100
}
Please advise how to calculate the cost of the below query
query {
first(limit: ${limit}) {
second(limit: ${limit}) {
int
}
anotherSecond(limit: ${limit}) {
int
}
}
}
Given here the cost map
type Query {
first (limit: Int): First @cost(
multipliers: ["limit"], useMultipliers: true, complexity: ${firstComplexity}
)
}
type First implements BasicInterface {
string: String
int: Int
second (limit: Int): Second @cost(
multipliers: ["limit"], useMultipliers: true, complexity: ${secondComplexity}
)
anotherSecond (limit: Int): Second @cost(
multipliers: ["limit"], useMultipliers: true, complexity: ${secondComplexity}
)
}
type Second implements BasicInterface {
int: Int
}
I believe this below is the correct test case, correct me if I was wrong
const firstCost = limit * firstComplexity
const secondCost = limit * limit * secondComplexity
const anotherSecondCost = limit * limit * secondComplexity
const result = firstCost + secondCost + anotherSecondCost
expect(visitor.cost).toEqual(result)
Reference:
iamake@2df09da
I think the cost calculated for nested siblings is treating the second sibling like it is a child. Here is a repro:
type Query {
things (limit: Int = 50): [Thing!]! @cost(multipliers: ["limit"], complexity: 1)
}
type Thing {
name: String!
subThingsA (limit: Int = 50): [SubThing!]! @cost(multipliers: ["limit"], complexity: 1)
subThingsB (limit: Int = 50): [SubThing!]! @cost(multipliers: ["limit"], complexity: 1)
}
type SubThing {
name: String!
}
Sample query:
query {
things {
subThingsA
subThingsB
}
}
Expected cost: 5050 (1 * 50) + (1 * 50 * 50) + (1 * 50 * 50)
Actual cost: 127550 (1 * 50) + (1 * 50 * 50) + (1 * 50 * 50 * 50)
I have a schema like this:
type PayrollType {
mou: String!
eou: String!
ecode: String!
payElements(filter: [String]): [PayElementType]
}
Here, I want to calculate the cost of payElements attribute where I pass the filter as an array of payelements for which I want to return the data like this: payElements(filter: ["ELEM1","ELEM2"])
So, here I want to base the multiplier based on length but as far as I know the filter can contain only integers as per this library. Any way to calculate multiplier cost by array length?
Here, the cost will be complexity*2 where 2 is the length of the array.
And before I forget to tell this, amazing work with the library. It works like a charm.
Currently, the validation rule requires providing the variables
object from the request because
This is needed because the variables are not available in the visitor of the graphql-js library.
Unfortunately, version 2 of Apollo Server no longer allows dynamic configuration of all of its server options per-request, but rather only the context
. validationRules
must be provided when the server is initialized and not within the context of the middleware. Therefore, all that each validation rule has access to is the ValidationContext
and there's no built-in way to inject the request variables.
I am reasonably certain that this is an intentional change.
It seems possible to remove graphql-cost-analysis
's dependence on the full variables
object, given that the ValidationContext
provides information about variable usages and also about field arguments.
Is this feasible?
When creating invalid queries, e.g. by using fields that do not exist we can bypass the complexity costs.
Let's say the request contains 30k very small and invalid queries, then we will have at least 30k errors in the response. I would have expected that this goes into the maximumCost
calculation when using defaultCost: 1
.
Hello everyone,
given a sample schema like:
type Query {
articles(limit: int): [Article]
}
type Article{
id: ID!
no: String!
country: ListEntry
}
type ListEntry {
id: ID!
descript: String
}
Howto define costs for Article and ListEntry types using the costMap? (can't use the SDL way as I'm going code first, not SDL first).
Until now I only understood how to define costs for top-level queries in general without taking the accessed fields into regard (that's what the example in the readme does).
But what I wish to define is:
articles: {
multipliers: ['limit'],
useMultipliers: true
},
Article: {
complexity: 1
},
ListEntry: {
complexity: 1
},
so that the query
query {
articles(10) {
no,
country {
descript
}
}
}
will fail when maximumCost
is set to 15, but will be fine when set to 20.
Any way to solve that with graphql-cost-analysis using costMap?
Regards,
Michael
Hello,
is there any way how to tell that if first
variable is not provided, it would default to 50, so multiplier should be 50 even without providing it ?
Anyone who has implemented this with Apollo Server 2? There is no example showing how to set up this.
Tried this but keeps throwing undefined errors
const server = new ApolloServer({
typeDefs: importSchema('./src/schema.graphql'),
resolvers: resolvers as any,
introspection: true,
playground: process.env.NODE_ENV === 'development',
debug: process.env.NODE_ENV === 'development',
context: async ({req}) => {
return {
req,
db
}
},
formatError: error => {
console.log('Error', error);
switch (error.extensions.code) {
case 'INTERNAL_SERVER_ERROR':
delete error.extensions.exception;
error.message = 'Internal server error. Try again later';
return error;
default:
return error;
}
},
validationRules: [
costAnalysis({
variables: graphQLParams.variables,
maximumCost: 1000,
}),
],
});
Any help would be greatly appreciated.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.