Code Monkey home page Code Monkey logo

openapi-psr7-validator's Issues

Relative paths in servers objects

When url field in server object containts relative path, for example "/v2" or "/", validator can't match request with operation definition.

For example, if we have definition:

servers:
  - url: '/v1'
paths:
  /operations:
    post:
...

and try to validate POST request "http://localhost/v1/operations", we get an exception "OpenAPI spec contains no such operation [/operations,post]". But if we specify server url as "http://localhost/v1", everything works fine.

If I understand it correctly, there is no possibility to match any request, when server path is relative (or if "servers" section is omitted altogether, and default value is one server with url="/").

From OpenAPI documentaion it is not very clear what to do in this case
https://swagger.io/docs/specification/api-host-and-base-path/#relative-urls
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#serverObject

The URLs in the servers array can be relative, such as /v2. In this case, the URL is resolved against the server that hosts the given OpenAPI definition.

Should validator ignore server name from request, when path is relative? Or somehow get "server that hosts the given OpenAPI definition" and match it to server name from request?

Do you have any suggestions?

Tries to validate content even if no content was specified

Hi,

When deleting a resource and the response has no content, the ResponseValidator still tries to validate the body:

// 2. Validate Body
try {
    $bodyValidator = new Body();
    $bodyValidator->validate($response, $spec->content);
} catch (Throwable $e) {
    ...

If my OAS response is defined like this:

delete:
  summary: Delete a resource
  responses:
    '204':
      description: No content

In the previous code sample $spec->content is an empty array.

The Body validator subsequently tries to get the Content-Type header which doesn't exist, and throws a NoContentType exception.

I think the whole body validation should be skipped if no content's been defined in the OAS specification (which is valid as per Swagger's doc).

EDIT: Or maybe still check whether there's a body despite the fact that no content was specified in the OAS spec, which I guess should throw an exception.

Invalid invalid type handling

If data type does not match schema code still tries to utilize it as a scalar value. moreother it tries utilize value itself rather than type, and this can lead to huge error texts

notice: "Array to string conversion in /data/www/content.leos.lamoda.ru/vendor/lezhnev74/openapi-psr7-validator/src/Schema/Keywords/Type.php on line 81"

I'll try to fix it

Caching of parsed OpenAPI definitions

This is more a question than anything. Hopefully it is covered somewhere.

So, for every inbound request to an API, the payload needs to be validated against an OpenAPI definition. That definition needs to be parsed/lexed/whatever into some form that can be used to do the validation. That will take time and resources to do.

So, is that parsed form of the OpenAPI definition cached? Can it even be cached? Does the full parsing happen on every API call, maybe it's not as resource intensive as I think it may be (but judging by the speed of client-based OpenAPI editors once a 3.0 definition goes above a few hundred lines, I would assume it is fairly intense).

I haven't tried it yet, but it looks ideal for a project I'm working on now.

Library does not play well with references

Given schema with reference and valid JSON input

        $yaml = /** @lang yaml */
            <<<'YAML'
openapi: 3.0.0
info:
  title: Product import API
  version: '1.0'
servers:
  - url: 'http://localhost:8000'
paths:
  /products.create:
    post:
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                test: 
                  $ref: "#/components/schemas/TestObject" 
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                properties:
                  result: 
                    type: string
components:
  schemas:
    TestObject:
      type: object
      required:
        - input
      properties:
        input:
          type: string
          example: 5201f887-e28a-45df-ad93-bde1021bae11
YAML;

        $cebeSchema = CebeReader::readFromYaml($yaml);
        $validator = new ServerRequestValidator($cebeSchema);

        $psrRequest = new ServerRequest(
            'POST',
            'http://localhost:8000/products.create',
            [
                'Content-Type' => 'application/json',
            ],
            <<<JSON
{
    "test": {
        "input": "some data"
    }
}
JSON
        );


            $validator->validate($psrRequest);

I expect validator to pass this object.

Actual results:

OpenAPI spec does not match the body of the request [/products.create,post]: All of the required rules must pass for { "test": `[object] (cebe\openapi\spec\Reference: { })` }

Because of https://github.com/lezhnev74/openapi-psr7-validator/blob/master/src/Schema/Keywords/Properties.php#L64
(each validator has this line)

If I comment out these lines and add the following hack

        $cebeSchema = CebeReader::readFromYaml($yaml);
+       $cebeSchema->setReferenceContext(new ReferenceContext($cebeSchema, '/'));
        $validator = new ServerRequestValidator($cebeSchema);

And add

            if ($schema instanceof Reference) {
                $schema = $schema->resolve();
            }

To each validator this starts working better, but it ends up here

https://github.com/lezhnev74/openapi-psr7-validator/blob/master/src/Schema/Keywords/Required.php#L61

since property could be a reference to a schema, not a real property.

Manually resolving all references before validation helps. but this looks too complicated to end user to know about manual references resolution

        $cebeSchema = CebeReader::readFromYaml($yaml);
        $cebeSchema->setReferenceContext(new ReferenceContext($cebeSchema, '/'));
+        $cebeSchema->resolveReferences();
        $validator = new ServerRequestValidator($cebeSchema);

Bug with oneOf and catch

If you have your definition with something like:

    SomeObjectHere:
      type: object
      properties:
        some_property_here:
          oneOf:
            - type: object
            - type: string
              maxLength: 50
              minLength: 1

You're supposed to accept requests like:

{
    "some_property_here": {}
}

and

{
    "some_property_here": "Some Value Here"
}

The second requests works fine but the first one complains that { } must be a string, because on lezhnev74/openapi-psr7-validator/src/Schema/Keywords/OneOf.php:

        foreach ($oneOf as $schema) {
            try {
                $schemaValidator->validate($data, $schema, $this->dataBreadCrumb);
                $matchedCount++;
            } catch (SchemaMismatch $e) {
                // that did not match... its ok
            }
        }

you're supposed to catch the exceptions that validate might throw, ignore them and count it. But for this scenario you're not catching the exception with the MaxLength:

        try {
            Validator::stringType()->assert($data);
            Validator::intType()->assert($maxLength);
            Validator::trueVal()->assert($maxLength >= 0);
        } catch (ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

and you're checking MaxLength for {} (which is not string) so Validator::stringType()->assert($data) fails, throwing InvalidSchema and you're catching only SchemaMismatch so either you do the checks for maxLength, minLength etc. only for correspondent types (iirc you can only apply certain constraints to certain types and you're checking all constraints for all types) or you add |InvalidSchema on OneOf and AnyOf.

For now I worked around this issue using AnyOf which processes until any criteria is valid and placing type: object before type: string so that it gets evaluated first.

[Feature] Incapsulate exceptions

Currently not only library exceptions are being thrown while validation, but also respect/validation library exception comes to the end user like

All of the required rules must pass for 18497

(expecting int[] here, not int)

It's very hard to manually process these exceptions and enrich their messages with things like validation data path where the error occured and also make end user create complex try..catch blocks.

I suggest to rethrow these exceptions and thus totally isolate end user from the used validation library knownledge. This will make library usage easier and allows further refactoring without being bond to the validation library because of exception contracts.

Also, at the moment, respect/validation exceptions should be part of validate method signature, since they are thrown and should be catched

Validation of callbacks

Some way to validate the request and response of callbacks would be great.

I think the validation per se would work, but there needs to be some other way to resolve the specs. Right now, the specs are loaded based on a passed OperationAddress which doesn't fit for callbacks.

Can we access parsed OpenApi schema after validation is done?

Validation logic parses and caches \cebe\openapi\spec\OpenApi internally. It uses it for validation purposes, but this instance (which is already in memory) can be valuable for later use AFTER validation is done.

I have this use case in my head:

  1. We built a validator from the yaml file.
  2. The schema is cached somewhere and used inside.
  3. After validation is complete, can we somehow continue use this finder to maybe find extra details about the matched request or response?
    3.1 RequestValidator returns OperationAddress, maybe we can use it to find extra details in the schema?

In other words, can we access the parsed \cebe\openapi\spec\OpenApi from the validator after the validation is done?

multipart with fields without content type won't pass validation

In #44 support for multipart request was improved. If you send fields with files (which have content-type), it works ok. But if a field without content type is included, an exception will be thrown:

Message: preg_match() expects parameter 2 to be string, null given
File: /app/vendor/lezhnev74/openapi-psr7-validator/src/PSR7/Validators/BodyValidator/MultipartValidator.php
Line: 178

Added a quick fix in fork

Collaboration

Hey there! I think there is a great chance for you folks to collaborate here. I am opening up the conversation here as it has the most stars, but it could be anywhere. They are all good implementations.

These packages all do the same thing.

  1. Read a JSON or YAML file
  2. Allow general PSR-7/PSR-15 middleware to be created for this file
  3. Offer at least request validation (maybe response too)
  4. Offer errors in various formats (RFC 7807 or others)
  5. Support custom formats

I think you could all team up on a new @thephpleague project. I bet they'd take you if you all worked together, I can provide some guidance if needed, and then you just archive things on GitHub, mark them abandoned on Packagist, and the mega-package lives on forever despite whatever hurdles life throw at you individually.

LMK if you like the sound of this. OpenAPI can be just too much for single maintainers to handle solo. 🤷‍♂

Missing inner exception message/detail when using AnyOf/OneOf

Not sure that could be considered as a bug or maybe an improvement...

When checking the solution applied for #57 I realised that this library discards the exceptions from the inner validations done with OneOf and AnyOf:

Here, for example src/Schema/Keywords/OneOf.php:

        foreach ($oneOf as $schema) {
            try {
                $schemaValidator->validate($data, $schema, $this->dataBreadCrumb);
                $matchedCount++;
            } catch (SchemaMismatch $e) {
                // that did not match... its ok         <<<<<<<<< HERE >>>>>>>>>>
            }
        }

        if ($matchedCount !== 1) {
            throw KeywordMismatch::fromKeyword('oneOf', $data, sprintf('Data must match exactly one schema, but matched %d', $matchedCount));
        }

so if we have in definition something like:

      properties:
        some_property_here:
          oneOf:
            - type: object
            - type: string
              maxLength: 10
              minLength: 1

And send a request with:

{
    "some_property_here": "String with more than 10 characters"
}

One would expected to see the message:
Keyword validation failed: Data must match exactly one schema, but matched 0 from the OneOf (which is ok and displaying correctly) but also the reason why it didn't match:
Length of '35' must be shorter or equal to 10 but this last message gets discarded when we catch SchemaMismatch $e and don't do anything with it...

So maybe there's a way to display those failed constraints and messages to get more specific errors?

The idea I had was to use the previous feature from exceptions as you're doing already in some places. So here (with some line breaks to make it more readable):

        $prev = null;
        foreach ($oneOf as $schema) {
            try {
                $schemaValidator->validate($data, $schema, $this->dataBreadCrumb);
                $matchedCount++;
            } catch (SchemaMismatch $e) {
                $prev = $e; // that did not match... its ok
            }
        }

...

            throw KeywordMismatch::fromKeyword(
                'oneOf',
                $data,
                sprintf('Data must match exactly one schema, but matched %d', $matchedCount),
                $prev // <<< HERE, there's already this argument in `fromKeyword` method
            );

Of course you need to consider that this catch block might get executed more than once, and you would need to somehow get/group all those inner exceptions (with my example I'm overriding $prev everytime)... idk, maybe we could start with at least 1 specific exception (which is better than none) and implement this multiple exception handling later 🤷‍♂ 🙂

Add body validation for "multipart/form-data" requests

Currently, the body of the request is validated as a string:

$body = (string) $message->getBody();

This does not work with schema validation at the moment. The problem is that we CAN specify multipart body format with a schema.

Review and decide how to change that.

Refs:

Exceptions consistency

Here I will create a list of inconsistent exceptions or exception hierarchy:

  • (https://github.com/lezhnev74/openapi-psr7-validator/blob/master/src/Schema/Keywords/Type.php#L113)[FormatMismatch] exception is derived from \LogicException, which according to the manual happens only if code logic is broken and there is a bug or misbehaviour in the library
  • Same stands for ValidationKeywordFailed
  • I'm not sure there is sense in catching any \Throwable since it too broad (i.e it would catch \ParseError and convert it to KeywordValidationFailed too)
  • Most other exceptions are derived from \RuntimeException, like MissedRequestCookie
  • MultipleOperationsMismatchForRequest extends Exception this looks correct but misaligned with other exceptions :)

Notes: \LogicException and \RuntimeException are kind of "Unchecked exceptions" analogue in PHP and they are often excluded from IDE static analisys by default

Handle JSON:API's application/vnd.api+json content type

Hi,

I'm currently trying out your package to run functional tests against an OpenAPI schema in a Laravel application.

We're following the JSON:API specification for our API, meaning the Content-Type and Accept headers are expected to be application/vnd.api+json.

But in the Body validator, the body will only be JSON-decoded if the Content-Type header is application/json.

Would it be possible to change the regex to also handle application/vnd.api+json?

Or are we expected to extend this behaviour somehow?

I'd also like to seize this opportunity to thank you for you work and encourage you to push it further - this is definitely headed in the right direction and very much needed by the PHP community!

Thanks,

Yannick

"Required" option for header parameters is ignored

Request validator ignores "required" option for header parameters, and treats all such parameters as mandatory, opposed to OpenAPI Specification https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#parameterObject.

required boolean Determines whether this parameter is mandatory. If the parameter location is "path", this property is REQUIRED and its value MUST be true. Otherwise, the property MAY be included and its default value is false.

Composite validation rules' related exceptions are lost

Hi,

The InvalidSchema exception (and maybe others) will only display the wrapping error and omit the related ones when the error is a composite one (such as Respect/Validation's AllOf).

The issue with this is we don't know what the errors actually are, which makes it really hard to troubleshoot, e.g:

OpenAPIValidation\PSR7\Exception\Response\ResponseBodyMismatch: OpenAPI spec does not match the body of the response [/api/test,get,200]: Schema(or data) validation failed: All of the required rules must pass for "{\"data\":\"test\"}"

This doesn't tell you what the actual (related) exceptions are.

Hope that makes sense!

Thanks,

Yannick

Validating non string query parameters

Hello,

I'm trying to validate a request containing two query parameters.

image

Upon doing so i am receiving a RequestQueryArgumentMismatch due to the query arguments of the request not being a type 'integer', and a 'string' given.

As of version 3.0 of openapi, query parameters can be specified as an integer along with other non string data types, i would have thought the PSR-7 validator would be able to look at a query parameter and check if it is an integer within a string.

https://swagger.io/docs/specification/describing-parameters/

Query string parameter validation of Arrays with `style: form` and `explode: false`

Consider this swagger parameter definition (in YAML):

    -
        name: filter
        in: query
        description: Filter something
        explode: false
        style: form
        schema:
            type: array
            items:
                type: string

According to the Open Api Specification this will result in a query parameter ?filter=a,b,c (which is also generated by Swagger UI)

However, the validation does not consider a URL of that format as an array, but as a string:

OpenAPI spec does not match the query argument 'with' of the request [/test,get]: Value expected to be 'array', 'string' given.

Needless dependency on symfony/yaml

Hi,

Related to #65 and idea of moving this package to php league.

I personally believe that in order to spread good practices and allow as many folks as possible to use openapi validation, we should focus on having as little dependencies as possible.

Currently, this package uses cebe/php-openapi which forces installation of symfony/yaml component. This indirectly forces a minimal version of Symfony (version >=4 I believe) and cuts off some potential developers. IMO input source (json/yaml) should be separated from parsing library. It should be up to developer to parse yaml/json (symfony/yaml could be a suggestion in composer.json) and then pass array or stdClass to cebe/php-openapi. This may seem like a bit of extra effort from developer but it will result in less dependencies and higher adoption.

Validation returns OperationAddress - just wondering why?

I'm trying a simple bit of validation:

$myJsonSpec = '{...}';
$serverRequest = // PSR-7 message constructed by hand
$validator = \OpenAPIValidation\PSR7\ServerRequestValidator::fromJson($myJsonSpec);
$result = $validator->validate($serverRequest);

If all validates correctly I get this object returned:

OpenAPIValidation\PSR7\OperationAddress

For example:

object(OpenAPIValidation\PSR7\OperationAddress)#47 (2) {
  ["method":protected]=>
  string(3) "get"
  ["path":protected]=>
  string(15) "/ccodes/{ccode}"
}

The method and the path are keys to the operation, so I'm wondering whether it would be more useful to return the operation? The operationId could then be used to fire off further parsing of the server request (I'm looking at generated code to do this). I may be missing the reason for returning OperationAddress though.

Without knowing the operationId at this point, further work needs to be done to decode the method and path to find it. It may already be available somewhere I'm not just looking.

Also, the validate() method either returns this object, or throws an exception. The exception is just for one of what may be many validation errors. Is there a way to find all the validation errors rather than just reporting on one? Looking at the validator, I can see it scatters _errors across many of the nodes of its internal data structures, so I'm guessing it does scan for multiple validation errors at once.

Automatically casts numeric strings to integer even if spec says it's a string

This issue is closely related to #58 .

I have a path parameter that is a string. Though, if I pass a string that is numeric such as "123" you automatically cast it to an integer:

https://github.com/lezhnev74/openapi-psr7-validator/blob/master/src/PSR7/OperationAddress.php#L98

This leads to the validation failing because the parameter is expected to be a string but it isn't because you cast it to an integer.

You should probably check if the specification says that the parameter is a string and if so don't cast it to an integer.

Validator ignores base uri path

Given openapi spec with
Server base uri http://localhost:8000/api/v1
POST operation /products.create

I expect POST request http://localhost:8000/api/v1/products.create to be valid

Actual result

OpenAPI spec contains no such operation [/api/v1/products.create,post]
        $yaml = /** @lang yaml */
            <<<YAML
openapi: 3.0.0
info:
  title: Product import API
  version: '1.0'
servers:
  - url: 'http://localhost:8000/api/v1'
paths:
  /products.create:
    post:
      requestBody:
        required: true
        content:
          application/json:
            schema:
              properties:
                test:
                  type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                properties:
                  result: 
                    type: string
YAML;

        $validator = new ServerRequestValidator(CebeReader::readFromYaml($yaml));

        $psrRequest = new ServerRequest(
            'POST',
            'http://localhost:8000/api/v1/products.create'
        );

        $validator->validate($psrRequest);

According to https://swagger.io/docs/specification/api-host-and-base-path/ having base url path prefix is totally valid

Bug with numeric strings

I have in definition something like:

    patch:
      summary: Do Something
      description: Do Something
      operationId: someOperation
      parameters:
        - name: id
          in: path
          description: ID of something
          required: true
          schema:
            type: string

and this throws me an error when the request contains a numeric string (which is possible):

Argument 2 passed to OpenAPIValidation\\PSR7\\Exception\\Validation\\InvalidPath::becauseValueDoesNotMatchSchema() must be of the type string, integer given, called in /srv/app/vendor/lezhnev74/openapi-psr7-validator/src/PSR7/Validators/PathValidator.php on line 56

That happens because on lezhnev74/openapi-psr7-validator/src/PSR7/Validators/PathValidator.php:

        foreach ($pathParsedParams as $name => $value) {
            try {
                $validator->validate($value, $specs[$name]->schema);
            } catch (SchemaMismatch $e) {
                throw InvalidPath::becauseValueDoesNotMatchSchema($name, $value, $addr, $e);
            }
        }

$value is integer; you're parsing parameters with:

            // cast numeric
            if (is_numeric($value)) {
                $value += 0; // that will cast it properly
            }

on lezhnev74/openapi-psr7-validator/src/PSR7/OperationAddress.php
but then on lezhnev74/openapi-psr7-validator/src/PSR7/Exception/Validation/InvalidPath.php you're type hinting with string:

    public static function becauseValueDoesNotMatchSchema(string $parameterName, string $parameterValue, OperationAddress $address, SchemaMismatch $prev) : self
    {
        $exception          = static::fromAddrAndPrev($address, $prev);
        $exception->message = sprintf('Value "%s" for parameter "%s" is invalid for %s', $parameterValue, $parameterName, $address);

so maybe you don't cast it or remove type hinting from InvalidPath?

Issues using readFromYamlFile() on Windows

There is an issue with the underlying cebe/php-openapi library for Windows users that prevents us from calling readFromYamlFile(). @scaytrase has opened an issue on their repo so hopefully it'll be fixed soon. However, this bug is fixed in the underlying library the examples in the README file will not work as described for Windows users.

In order to read the YAML file I had to do something slightly different to the example:

$yamlFile = "api.yaml";

$validator = new \OpenAPIValidation\PSR7\ResponseValidator(
    \cebe\openapi\Reader::readFromYaml(file_get_contents($yamlFile))
);

$operation = new \OpenAPIValidation\PSR7\OperationAddress('/password/gen', 'get') ;

$validator->validate($operation, $request);

Would you like me to submit a PR to update the README examples to use file_get_contents() as a temporary solution?


I have also found that I am unable to run the test suite for the same reason. For reference, this is the error I get:

OpenAPIValidationTests\PSR7\CompleteTest::test_request_green
cebe\openapi\exceptions\UnresolvableReferenceException: Can not resolve references for a specification given as a relative path.

I tried updating the failing test to use file_get_contents() but I get another error:

OpenAPIValidationTests\PSR7\CompleteTest::test_request_green
OpenAPIValidation\PSR7\Exception\Request\RequestBodyMismatch: OpenAPI spec does not match the body of the request [/complete/{param1}/{param2},post]: Argument 1 passed to OpenAPIValidation\Schema\Validator::__construct() must be an instance of cebe\openapi\spec\Schema, instance of cebe\openapi\spec\Reference given

I'll have a crack at getting the test suite to run but I thought I'd bring this up in case it's affecting anyone else.

Content-Type:application/json with charset

So i'm trying out your project, and i'm a huge fan.

Well i'm designing a restful api, and i'm testing my responses.
While i was doing so, i found out that your body validator, doesn't like if the content-type is having a charset. This is kind of the standard of the framework that i'm using for the restapi.

As seen in the documentation from mozilla, there it's fully allowed to have both charset, and boundry as a parameter. link

Symptoms

When the response is validated, it's not taken as json, even though application/json is in the content-type. Therefore, the response-body is not json decoded and taken as a string instead.

Example

application/json; charset=UTF-8

Suggested fix

/PSR7/Validators/Body.php:43 change '#^application/.*json$#' to '#^application/.*json#'
Now i know, this isn't bulletproof because it's not ending up with json, as the last word, but it allows a the charset string to be implementet.
I have though tested this and it now

Improve urlencoded body parsing

When you have an urlencoded body with arrays, and the field is required according to the specification, validation won't pass.

I've added an array field to the message in tests (BodyValidatorTest::dataProviderFormUrlencodedGreen): (phones[0]=123-456&phones[1]=456-789. When urlencoded, the full message will be:

address=Moscow%2C+ulitsa+Rusakova%2C+d.15&id=59731930-a95a-11e9-a2a3-2a2ae2dbcce4&phones%5B0%5D=123-456&phones%5B1%5D=456-789

After current body parsing (FormUrlencodedValidator::parseUrlencodedData), it will result in:

Array
(
[address] => Moscow%2C+ulitsa+Rusakova%2C+d.15
[id] => 59731930-a95a-11e9-a2a3-2a2ae2dbcce4
[phones%5B0%5D] => 123-456
[phones%5B1%5D] => 456-789
)

If you have declared "phones" as a required field, validation will fail because this field is not present in the parsed body.

Get the name of the invalid property

I'm trying to use this library to generate error messages that are visible to the end-user. Thus I need a way to find out the name of the property that's not valid.

As far as I can tell there is no way to this right now. I'm thinking of something like that:

try {
 // Validation
} catch (OpenAPIValidation\PSR7\Exception\Validation\InvalidBody $e) {
    $property = $e->getInvalidPropertyName();
    echo "The field '$property' is not valid. Please correct it and try again."
}

Is something like that possible and if not are there any plans to implement this?

Thanks

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.