Coder Social home page Coder Social logo

jesse's Introduction

This repository is no longer actively maintained, please see https://github.com/for-GET/jesse

===== jesse Build Status

jesse (JSon Schema Erlang) is an implementation of a json schema validator for Erlang.

jesse implements [Draft 03] (http://tools.ietf.org/html/draft-zyp-json-schema-03) of the specification. It supports almost all core schema definitions except:

  • format
  • $ref

Quick start

There are two ways of using jesse:

  • to use jesse internal in-memory storage to keep all your schema definitions In this case jesse will look up a schema definition in its own storage, and then validate given json.
  • it is also possible to provide jesse with schema definitions when jesse is called.

Examples

NOTE: jesse doesn't have any parsing functionality. It currently works with three
      formats: mochijson2, jiffy and jsx, so json needs to be parsed in advance,
      or you can specify a callback which jesse will use to parse json.

      In examples below and in jesse test suite jiffy parser is used.
  • Use jesse's internal in-memory storage:

(parse json in advance)

1> Schema = jiffy:decode(<<"{\"items\": {\"type\": \"integer\"}}">>).
{[{<<"items">>,{[{<<"type">>,<<"integer">>}]}}]}
2> jesse:add_schema(some_key, Schema).
ok
3> Json1 = jiffy:decode(<<"[1, 2, 3]">>).
[1,2,3]
4> jesse:validate(some_key, Json1).
{ok,[1,2,3]}
5> Json2 = jiffy:decode(<<"[1, \"x\"]">>).
[1,<<"x">>]
6> jesse:validate(some_key, Json2).
{error,[{data_invalid,{[{<<"type">>,<<"integer">>}]},
                      wrong_type,<<"x">>,
                      [1]}]}

The [1] in the error is the path in the original value to <<"x">> where the validation failed. See Validation errors below for the full error format.

(using a callback)

1> jesse:add_schema(some_key,
1>                  <<"{\"uniqueItems\": true}">>,
1>                  [{parser_fun, fun jiffy:decode/1}]).
ok
2> jesse:validate(some_key,
2>                <<"[1, 2]">>,
2>                [{parser_fun, fun jiffy:decode/1}]).
{ok,[1, 2]}
3> jesse:validate(some_key,
3>                <<"[{\"foo\": \"bar\"}, {\"foo\": \"bar\"}] ">>,
3>                [{parser_fun, fun jiffy:decode/1}]).
{error,[{data_invalid,{[{<<"uniqueItems">>,true}]},
                      {not_unique,{[{<<"foo">>,<<"bar">>}]}},
                      [{[{<<"foo">>,<<"bar">>}]},{[{<<"foo">>,<<"bar">>}]}],
                      []}]}
  • Call jesse with schema definition in place (do not use internal storage)

(parse json in advance)

1> Schema = jiffy:decode(<<"{\"pattern\": \"^a*$\"}">>).
{[{<<"pattern">>,<<"^a*$">>}]}
2> Json1 = jiffy:decode(<<"\"aaa\"">>).
<<"aaa">>
3> jesse:validate_with_schema(Schema, Json1).
{ok,<<"aaa">>}
4> Json2 = jiffy:decode(<<"\"abc\"">>).
<<"abc">>
5> jesse:validate_with_schema(Schema, Json2).
{error,[{data_invalid,{[{<<"pattern">>,<<"^a*$">>}]},
                      no_match,
                      <<"abc">>,[]}]}

(using a callback)

1> Schema = <<"{\"patternProperties\": {\"f.*o\": {\"type\": \"integer\"}}}">>.
<<"{\"patternProperties\": {\"f.*o\": {\"type\": \"integer\"}}}">>
2> jesse:validate_with_schema(Schema,
2>                            <<"{\"foo\": 1, \"foooooo\" : 2}">>,
2>                            [{parser_fun, fun jiffy:decode/1}]).
{ok,{[{<<"foo">>,1},{<<"foooooo">>,2}]}}
3> jesse:validate_with_schema(Schema,
3>                            <<"{\"foo\": \"bar\", \"fooooo\": 2}">>,
3>                            [{parser_fun, fun jiffy:decode/1}]).
{error,[{data_invalid,{[{<<"type">>,<<"integer">>}]},
                      wrong_type,<<"bar">>,
                      [<<"foo">>]}]}
  • Since 0.4.0 it's possible to say jesse to collect errors, and not stop immediately when it finds an error in the given json:
1> Schema = <<"{\"properties\": {\"a\": {\"type\": \"integer\"}, \"b\": {\"type\": \"string\"}, \"c\": {\"type\": \"boolean\"}}}">>.
<<"{\"properties\": {\"a\": {\"type\": \"integer\"}, \"b\": {\"type\": \"string\"}, \"c\": {\"type\": \"boolean\"}}}">>
2> jesse:validate_with_schema(Schema,
2>                            <<"{\"a\": 1, \"b\": \"b\", \"c\": true}">>,
2>                            [{parser_fun, fun jiffy:decode/1}]).
{ok,{[{<<"a">>,1},{<<"b">>,<<"b">>},{<<"c">>,true}]}}

now let's change the value of the field "b" to an integer

3> jesse:validate_with_schema(Schema,
3>                            <<"{\"a\": 1, \"b\": 2, \"c\": true}">>,
3>                            [{parser_fun, fun jiffy:decode/1}]).
{error,[{data_invalid,{[{<<"type">>,<<"string">>}]},
                      wrong_type,2,
                      [<<"b">>]}]}

works as expected, but let's change the value of the field "c" as well

4> jesse:validate_with_schema(Schema,
4>                            <<"{\"a\": 1, \"b\": 2, \"c\": 3}">>,
4>                            [{parser_fun, fun jiffy:decode/1}]).
{error,[{data_invalid,{[{<<"type">>,<<"string">>}]},
                      wrong_type,2,
                      [<<"b">>]}]}

still works as expected, jesse stops validating as soon as finds an error. let's use the 'allowed_errors' option, and set it to 1

5> jesse:validate_with_schema(Schema,
5>                            <<"{\"a\": 1, \"b\": 2, \"c\": 3}">>,
5>                            [{parser_fun, fun jiffy:decode/1},
5>                             {allowed_errors, 1}]).
{error,[{data_invalid,{[{<<"type">>,<<"boolean">>}]},
                      wrong_type,3,
                      [<<"c">>]},
        {data_invalid,{[{<<"type">>,<<"string">>}]},
                      wrong_type,2,
                      [<<"b">>]}]}

now we got a list of two errors. let's now change the value of the field "a" to a boolean

6> jesse:validate_with_schema(Schema,
6>                            <<"{\"a\": true, \"b\": 2, \"c\": 3}">>,
6>                            [{parser_fun, fun jiffy:decode/1},
6>                             {allowed_errors, 1}]).
{error,[{data_invalid,{[{<<"type">>,<<"string">>}]},
                      wrong_type,2,
                      [<<"b">>]},
        {data_invalid,{[{<<"type">>,<<"integer">>}]},
                      wrong_type,true,
                      [<<"a">>]}]}

we stil got only two errors. let's try using 'infinity' as the argument for the 'allowed_errors' option

7> jesse:validate_with_schema(Schema,
7>                            <<"{\"a\": true, \"b\": 2, \"c\": 3}">>,
7>                            [{parser_fun, fun jiffy:decode/1},
7>                             {allowed_errors, infinity}]).
{error,[{data_invalid,{[{<<"type">>,<<"boolean">>}]},
                      wrong_type,3,
                      [<<"c">>]},
        {data_invalid,{[{<<"type">>,<<"string">>}]},
                      wrong_type,2,
                      [<<"b">>]},
        {data_invalid,{[{<<"type">>,<<"integer">>}]},
                      wrong_type,true,
                      [<<"a">>]}]}

Json Schema versions

Currently there're two drafts of Json Schema: draft3 and draft4. Currently jesse supports only draft3, but the architecture allows to extend jesse to support any schema formats. To decide which validator to use jesse tries to read $schema property from the given schema, and checks if it's a supported one, otherwise it will return an error. If $schema property isn't provided in the given schema, jesse will use the default validator (currently the validator for draft3).

To specify which validator to use by default (if there's no $schema property in the given schema), one should use 'default_schema_ver' option when call jesse:validate/3 or jesse:validate_with_schema/3, the value should be a binary consisting a schema path, i.e. <<"http://json-schema.org/draft-03/schema#">>.

Validation errors

The validation functions jesse:validate/2 and jesse:validate_with_schema/2,3 return {ok, Value} on success and {error, ListOfErrors} on failure. An error is either data_invalid or schema_invalid.

A data_invalid error is a tuple on the form {data_invalid, Schema, ErrorType, Value, Path} where

  • Schema is the part of the schema where validation failed
  • ErrorType is the type of error, usually an atom such as wrong_type, not_in_range or no_match
  • Value is The part of the value where failed validation agains Schema
  • Path is a path to where validation failed within the original value. The path is a list of property names and zero-based array indices referencing the properties and array items within a JSON document; e.g. in the JSON document {"foo": [42, 43, 44]}, the path [<<"foo">>, 0] refers to the value 42. An empty list refers to the whole JSON document.

A schema_invalid error is a tuple on the form {schema_invalid, Schema, ErrorType} where

  • Schema is the part of the schema which is invalid
  • ErrorType is an atom such as missing_id_field or a tuple such as {wrong_type_dependency, Dependency}.

Caveats

  • pattern and patternProperty attributes:

    jesse uses standard erlang module re for regexp matching, therefore there could be some incompatible regular expressions in schemas you define.

    From erlang docs: "re's matching algorithms are currently based on the PCRE library, but not all of the PCRE library is interfaced"

    But most of common cases should work fine.

Contributing

If you see something missing or incorrect, a pull request is most welcome!

jesse's People

Contributors

andreineculau avatar cy6erbr4in avatar dajoh avatar eliadil avatar keynslug avatar loucash avatar mootboy avatar nifoc avatar richcarl avatar zuiderkwast avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jesse's Issues

improve types in jesse.erl

 andreineculau 2 days ago 
I personally would have liked 1) the error to be more uniform 2) the errors to be categorized e.g.

-type error_reason() :: { 'schema_invalid'
                        , Schema :: json_term()
                        , Error :: schema_error()
                        }
                      | { 'data_invalid'
                        , Schema :: json_term()
                        , Error  :: data_error()
                        , Data   :: json_term()
                        }.

-type schema_error() :: {schema_error_id(), Details :: any() }
-type schema_error_id() :: 'missing_id_field'
                         | ...

-type data_error() :: {data_error_id(), Details :: any() }
-type data_error_id() :: 'missing_required_property'
                       | ...
Reason: improved/clearer documentation IMO, and cleaner pattern matching.

Just a thought, @Cy6erBr4in :) Nice effort

PS: the above is just to illustrate the pattern; the type definition (for Details) can be more explicit per *errorid and more documentation-driven

jsx support

Can jesse be made to support jsx's JSON format (it uses proplists for objects, as opposed to proplists enclosed into a tuple)?

xref undef fun warning

[me@localdomain:/tmp/jesse] $ git checkout 1.1.1
[me@localdomain:/tmp/jesse] $ make
==> jesse (get-deps)
==> jesse (compile)
Compiled src/jesse_error.erl
Compiled src/jesse.erl
Compiled src/jesse_database.erl
Compiled src/jesse_lib.erl
Compiled src/jesse_schema_validator.erl
Compiled src/jesse_json_path.erl
Compiled src/jesse_state.erl
Compiled src/jesse_validator_draft3.erl
[me@localdomain:/tmp/jesse] $ rebar xref
==> jesse (xref)
src/jesse_lib.erl:46: Warning: jesse_lib:get_schema_id/1 calls undefined function jesse_json_path:get_value/3 (Xref)

Property path in the validation errors

The validation errors don't contain any clue to which of the properties has failed validation. In the example below, the wrong_type error could refer to either of the properties "foo" or "bar".

1> Schema = <<"{\"properties\": {\"foo\": {\"type\": \"integer\"}, \"bar\": {\"type\": \"integer\"}}}">>,
1> Data   = <<"{\"foo\": \"baz\", \"bar\": \"baz\"}">>,
1> jesse:validate_with_schema(Schema, Data, [{parser_fun, fun jiffy:decode/1}]).
{error,[{data_invalid,{[{<<"type">>,<<"integer">>}]},
                      wrong_type,<<"baz">>}]}

I suggest that the path to the failing property be added to the error tuple in a form such as <<"#/foo">> or [<<"foo">>].

For comparison, JSV (the JavaScript JSON Schema validator, https://github.com/garycourt/JSV) returns both the path in the data and the path in the schema to where the validation error occurred.

add jesse:error_to_json

Hi, I was thinking it'd be really nice to have a pre-build function that formats the erlang error into a json error to return to the sender.

It probably can be done easily in the application using jesse but it seems such a common thing that I feel like it could be included directly?

-spec error_to_json([jesse:error()]) -> binary().
%% ...

something like that?

Format not supported?

Thanks a lot for jesse ๐Ÿ˜„ Any chance the format attribute will get supported?

Dot used in Keys

If a key is created with a dot(.) in them such as {"3.4.5.6.7":"Hello world!"}. Jesse is not able to validate it back to a schema.

We think this is due to path traversing functionality that is trying to use "." as part of Json notation while it is actually part of the key itself.

not actively maintained?

Based on the lack of commits (last commit was 1y ago), lack of activity in issues/pull requests #10 #16 #34 #35 #36 #37 #38, and also my conversation with the author/main maintainer of jesse, I guess this repository is not actively maintained anymore.

If that's correct, can we apply the same practice as we did with katt i.e. add a note in the README and link to https://github.com/for-GET/jesse ?

Authors of jesse will continue maintenance on that fork, and we'll tackle current pull requests there.

Thank you.
// cc @richcarl @tjarvstrand

"wrong_type" error instead of "missing_required_property" for validate(jsx:decode(<<"{}">>))

I have the following schema:

Schema = 
    [
        {<<"type">>, <<"object">>},
        {<<"properties">>, [
            {<<"foo">>, [
                {<<"type">>, <<"string">>},
                {<<"required">>, true}
            ]}
        ]}
    ].

jesse:validate_with_schema/2 returns wrong error if try to validate empty JSON:
jesse:validate_with_schema(Schema, jsx:decode(<<"{}">>)) returns "wrong_type" error, but "missing_required_property" is expected here.
It works fine if to decode JSON using jiffy - jesse:validate_with_schema(Schema, jiffy:decode(<<"{}">>)) returns expceted "missing_required_property" error. Differense is in values returned from decode functions. jsx:decode() returns [{}], and jiffy:decode returns {[]}

Update error_reason type in jesse.erl

As the documentation points out, a data_invalid error should be of the form {data_invalid, Schema, ErrorType, Value, Path} but in jesse.erl the type error_reason is defined as:

-type error_reason() :: { 'schema_invalid'
                        , Schema :: json_term()
                        , Error :: error_type()
                        }
                      | { 'data_invalid'
                        , Schema :: json_term()
                        , Error :: error_type()
                        , Data :: json_term()
                        }.

Fix it so they are consistent.

Update to Draft 4 of JSON-Schema

What needs to be done

  • Small adjustments of current code (some renamed things: divisibleBy to multipleOf, readonly to readOnly, ...)
  • Add the new things that are not yet implemented to the README.

Considerations

  • Should we keep backwards compatibility with draft 3 when possible (keep renamed and/or deleted things: readonly, divisibleBy, extends, disallow, type=any) or drop them?
  • A flag for which draft to validate against? (Intuitively, I doen feel it's an attractive thing to do.)

Change logs for draft 4

Core schema:

  • Initial draft.
  • Salvaged from draft v3.
  • Mandate the use of JSON Reference, JSON Pointer.
  • Define the role of "id". Define URI resolution scope.
  • Add interoperability considerations.

Validation schema:

  • Initial draft.
  • Salvaged from draft v3.
  • Redefine the "required" keyword.
  • Remove "extends", "disallow"
  • Add "anyOf", "allOf", "oneOf", "not", "definitions",
    "minProperties", "maxProperties".
  • "dependencies" member values can no longer be single strings;
    at least one element is required in a property dependency
    array.
  • Rename "divisibleBy" to "multipleOf".
  • "type" arrays can no longer have schemas; remove "any" as a
    possible value.
  • Rework the "format" section; make support optional.
  • "format": remove attributes "phone", "style", "color"; rename
    "ip-address" to "ipv4"; add references for all attributes.
  • Provide algorithms to calculate schema(s) for array/object
    instances.
  • Add interoperability considerations.

Hyper schema:

  • Resolution of link URIs ("href") is now affected by rel="self"
    links on the instance
  • Define "title" for LDOs
  • Use URI Templates for the "href" property
  • Split hyper-schema definition out from main schema.
  • Capitalised the T in "encType", and the O in "readOnly"
  • Moved "mediaType" and "contentEncoding" to the new "media"
    property (renamed "type" and "binaryEncoding")
  • Added "mediaType" property to LDOs
  • Replaced "slash-delimited" fragment resolution with "json-
    pointer".
  • Added "template" LDO attribute.
  • Improved wording of sections.

failed to validate array with different types of complex objects

As requested this is a follow up on #30.

When using this schema as vnd.1and1.mams.user-agenda-modify-v1+json:

{
    "title" : "AgendaModify",
    "type" : "object",
    "properties" : {
        "add" : {
            "type" : "object",
            "required" : true,
            "properties" : {
                "contacts" : {
                    "type" : "array",
                    "required" : true,
                    "items" : {
                        "type" : [
                            {
                                "type" : "object",
                                "properties" : {
                                    "phone" : {
                                        "type": "string",
                                        "pattern" : "^\+\d{10,15}$",
                                        "required" : true
                                    }
                                },
                                "additionalProperties" : false
                            },
                            {
                                "type" : "object",
                                "properties" : {
                                    "email" : {
                                        "type" : "string",
                                        "pattern" : "^\S+@\S+\.\S+$",
                                        "required" : true
                                    }
                                },
                                "additionalProperties" : false
                            }
                        ]
                    },
                    "additionalItems" : false
                }
            },
            "additionalProperties" : false
        }
    },
    "additionalProperties" : false
}

calling jesse:validate on

<<"vnd.1and1.mams.user-agenda-modify-v1+json">>,
<<"{\n  \"add\": {\n           \"contacts\": [\n                         { \"phone\": \"+46737341553\" },\n                         { \"email\": \"[email protected]\" }\n                       ]\n         }\n}">>

results in the following (unexpected) error

  [
   {data_invalid,
    {struct,[{<<"type">>,[{struct,[{<<"type">>,<<"object">>},{<<"properties">>,{struct,[{<<"phone">>,{struct,[{<<"type">>,<<"string">>\
},{<<"pattern">>,<<"^\\+\\d{10,15}$">>},{<<"required">>,true}]}}]}},{<<"additionalProperties">>,false}]},{struct,[{<<"type">>,<<"objec\
t">>},{<<"properties">>,{struct,[{<<"email">>,{struct,[{<<"type">>,<<"string">>},{<<"pattern">>,<<"^\\S+@\\S+\\.\\S+$">>},{<<"required\
">>,true}]}}]}},{<<"additionalProperties">>,false}]}]}]},
    wrong_type,
    {struct,[{<<"email">>,<<"[email protected]">>}]},[<<"add">>,<<"contacts">>,1]},
   {data_invalid,
    {struct,[{<<"type">>,[{struct,[{<<"type">>,<<"object">>},{<<"properties">>,{struct,[{<<"phone">>,{struct,[{<<"type">>,<<"string">>\
},{<<"pattern">>,<<"^\\+\\d{10,15}$">>},{<<"required">>,true}]}}]}},{<<"additionalProperties">>,false}]},{struct,[{<<"type">>,<<"objec\
t">>},{<<"properties">>,{struct,[{<<"email">>,{struct,[{<<"type">>,<<"string">>},{<<"pattern">>,<<"^\\S+@\\S+\\.\\S+$">>},{<<"required\
">>,true}]}}]}},{<<"additionalProperties">>,false}]}]}]},
    wrong_type,
    {struct,[{<<"phone">>,<<"+46737341553">>}]},[<<"add">>,<<"contacts">>,0]}
  ].

Multiple error communicates.

Hello, just a quick question
Is it possible to get, for example, multiple missing_required_property from validation?

Example
My json looks like this
{
"name" : "asdf",
"surname" : "fdsa"
}
and both fields are marked as required in schema. But when i pass an empty json, validation only returns one missing_required_property.

validator per $schema

Tightly linked to #2 is the ability to associate a validator with each $schema.
Schemas can reference other schemas that are not of the same JSON Schema version, nor of JSON Schema per say

eg. associate http://json-schema.org/draft-04/hyper-schema# with a jesse_schema_validator_v4 (doesn't have to be built into jesse)

Empty list "[]" considered valid value for property of type "string" (and probably others)

I have the following schema:

{
    "$schema": "http://json-schema.org/draft-03/schema#",

    "type":      "object",
    "title":     "test message",
    "required":  true,

    "additionalProperties": false,

    "properties": {
        "note": {
            "type":      "string",
            "required":  false
        }
    }
}

The following JSON is (incorrectly) considered valid, according to jesse:validate/2:

{
    "note": []
}

The following JSON is considered invalid, as expected:

{
    "note": 12345
}

Finally, I apologize for my fat fingers, resulting in my closing and then re-opening this issue.

Items in array of objects not validated...

Hi,
I have a schema that contains an array of objects. Here's the troublesome part of it:
"mids" : {
"minItems" : 1,
"required" : true,
"type" : "array",
"items" : {
"mid" : {
"type" : "object",
"required" : true,
"properties" : {
"key" : {
"required" : true,
"type" : "string"
},
"mcc" : {
"required" : true,
"type" : "integer"
},
"country" : {
"required" : true,
"type" : "string",
"minLength" : 2
},
"clientname" : {
"required" : true,
"type" : "string"
},
"saleschannel" : {
"required" : true,
"type" : "string"
},
"branch" : {
"required" : false,
"type" : "string"
...
What happens is that if you omit a property from mid object, even if it is marked as required, validation returns OK.
Example:
"mids" : [
{"mid" : {
"key" : "TMFH8999",
"mcc" : 8999,
"clientname" : "IRITEL",
"saleschannel" : "EPTA",
...
Validation returns OK even if the country property is omitted.

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.