jskov / openapi-jaxrs-client Goto Github PK
View Code? Open in Web Editor NEWAn OpenAPI JAX-RS client code generator
License: Apache License 2.0
An OpenAPI JAX-RS client code generator
License: Apache License 2.0
Nexus API has two entries returning 200 with no type definintion. This causes a NPE.
/v1/iq:
get:
tags:
- "Manage IQ server configuration"
summary: "Get IQ server configuration"
description: ""
operationId: "getConfiguration"
consumes:
- "application/json"
produces:
- "application/json"
parameters: []
responses:
"200":
description: "IQ server configuration returned"
Add something like (for Api and Dto):
postProcessApi(Function<String, String>)
postProcessApi(Pattern, Function<String, String>)
To allow simple hacks such has:
postProcessApi(s -> s.replace("@HeaderParam(\"Authorization\") String auth", "@HeaderParam(\"Authorization\") String auth, @HeaderParam(\"x-api-key\") String xApiKey")
I am experiencing issues when generating DTO models from a spec with properties nested inside the allOf tag for models with inhertiance. If I move properties and tags other than " $ref" outside the allOf tag it seems to work.
Concretely extends is missing on model with nested properties in allOf, but present as it should be when properties is moved outside of allOf tag.
This exact combination is used in Swaggers own example for inheritance in their documentation, so I think this should be supported? https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/
I have included the snippet for the models with inheritance below (file upload did not seem to work properly?)
SMSMailIdentification has the problem (original spec)
VirkPersonIdentification works as it should (identation modified)
VirkPersonIdentification:
allOf:
- $ref: '#/components/schemas/Identification'
properties:
virkUnitNumber:
description: 'DA: Virk.dk person id<br/>The id for a person at virk.dk'
type: string
description: 'DA: Virk.dk registreret person-id. <br/>Virk.dk registered personId'
type: object
SMSMailIdentification:
allOf:
- $ref: '#/components/schemas/Identification'
- type: object
properties:
countryCode:
description: 'DA: Landekode for telefonnummeret<br/>The country code of
the phone number'
example: +45
type: string
phoneNumber:
description: 'DA: Telefonnummer til brug ved SMS login<br/>The phone number
to use for SMS login. Maximum of 30 digits excluding spaces allowed.'
example: 12345678
maxLength: 40
type: string
description: 'DA: SMS og mail identifikation<br/>SMS and mail identification'
IdentificationType:
enum:
- VIRK_PERSON
- SMS_MAIL
type: string
Identification:
required:
- type
properties:
type:
$ref: '#/components/schemas/IdentificationType'
locked:
default: false
description: If identification is protected by a secret, this field is true.<br>When
true the following operations can only be done when the known secret is
supplied in header x-locked-secret.<ul><li>PATCH of contact with a different
identification type than already set.</li></ul>
readOnly: true
type: boolean
loginId:
description: 'DA: Login id<br/>Login id, from the security token.'
readOnly: true
type: string
description: '
<b>NOTE: Discriminator mapping is not shown in Swagger UI (as of september 2023).
Read the .yaml spec file directly for the discriminator mapping or use an alternative UI, for example Redoc.</b>
<br/><br/>
The identification of the contact. Can only be changed when identification.loginId
is not set.
'
discriminator:
mapping:
VIRK_PERSON: '#/components/schemas/VirkPersonIdentification'
SMS_MAIL: '#/components/schemas/SMSMailIdentification'
propertyName: type
oneOf:
- $ref: '#/components/schemas/VirkPersonIdentification'
- $ref: '#/components/schemas/SMSMailIdentification'
nullable: true
type: object
Similar to generator-jackson-localdate-*
With generator-api-skip = true
I get:
Caused by: java.lang.IllegalArgumentException: The property generator-api-package must be specified!
at dk.mada.jaxrs.generator.GeneratorOpts.getRequired(GeneratorOpts.java:392)
at dk.mada.jaxrs.generator.GeneratorOpts.apiPackage(GeneratorOpts.java:97)
at dk.mada.jaxrs.generator.GeneratorOpts.apiPackageDir(GeneratorOpts.java:102)
at dk.mada.jaxrs.Generator.generateClient(Generator.java:64)
... 33 more
And when empty, follow the naming of naming-rules-type.
Current generator will make from:
post:
tags:
- templates
requestBody:
content:
multipart/form-data:
schema:
required:
- template-jar
- jar-size
type: object
properties:
metadata:
$ref: '#/components/schemas/TemplateDTO'
template-jar:
format: binary
type: string
jar-size:
format: int64
type: integer
responses:
"201":
description: Created
"401":
description: Not Authorized
"403":
description: Not Allowed
This API call:
void templatesPost(@HeaderParam("Authorization") String auth, @Valid @FormParam("metadata") TemplateDTO metadata, @FormParam("template-jar") byte[] templateJar, @FormParam("jar-size") long jarSize, Object dto);
But what is needed is more like this:
void templatesPost(@HeaderParam("Authorization") String auth, @MultipartForm CreateMultiPartBody dto);
And a companion builder for:
public static class CreateMultiPartBody {
@FormParam("metadata")
@PartType(MediaType.APPLICATION_JSON)
@NotNull
@Valid
public final TemplateDTO dto;
@FormParam("template-jar")
@PartType(MediaType.APPLICATION_OCTET_STREAM)
@NotNull
public final InputStream templateJar;
@NotNull
@FormParam("jar-size")
@PartType(MediaType.APPLICATION_JSON)
public final long jarSize;
public CreateMultiPartBody(TemplateDTO dto, InputStream templateJar, long jarSize) {
this.dto = dto;
this.templateJar = templateJar;
this.jarSize = jarSize;
}
@Override
public String toString() {
return "CreateMultiPartBody [metadata=" + dto + ", template-jar=" + templateJar + ", jar-size=" + jarSize + "]";
}
}
Otherwise the Quarkus REST client will balk (see quarkusio/quarkus#12286).
Not sure if this is a workaround, or just the "Right Way(tm)". Seems that this is outside the spec, and what resteasy provides.
Today, the types map like:
integer->short,
integer,int32->int,
integer,int64->long
But some (broken, unclear?) documents have just "integer" and expect to use int.
And option to add the @Schema(name) to DTOs.
Explore option to allow per-type-name overrides (maybe just use REGEXP?)
Only add the name= option when it differs from the type so it is more visible.
Annotations
4 warnings
Build
Node.js 12 actions are deprecated. Please update the following actions to use Node.js 16: actions/checkout@v2, actions/cache@v1. For more information see: https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/.
Build
The save-state
command is deprecated and will be disabled soon. Please upgrade to using Environment Files. For more information see: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
Build
The save-state
command is deprecated and will be disabled soon. Please upgrade to using Environment Files. For more information see: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
Build
The set-output
command is deprecated and will be disabled soon. Please upgrade to using Environment Files. For more information see: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
This 200 response without data type:
/api/aktiviteter:
get:
responses:
"200":
description: OK
Results in failure:
Caused by: java.lang.NullPointerException: null at: baseType
at io.jstach.jstachio.formatters.DefaultFormatter.format(DefaultFormatter.java:50)
at dk.mada.jaxrs.generator.api.tmpl.CtxApiRenderer.render(CtxApiRenderer.java:745)
at dk.mada.jaxrs.generator.api.tmpl.CtxApiRenderer.execute(CtxApiRenderer.java:94)
at dk.mada.jaxrs.generator.api.tmpl.CtxApiRenderer.execute(CtxApiRenderer.java:85)
at dk.mada.jaxrs.generator.api.tmpl.CtxApiRenderer.process(CtxApiRenderer.java:161)
It should instead fail in parsing, and suggest use of option: parser-fixup-void-200-to-204
For example:
swagger: "2.0"
info:
version: "3.38.0-01"
title: "Nexus Repository Manager REST API"
basePath: "/service/rest/"
...
/v1/components/{id}:
post:
Spent some time not understading why the post did not work at /v1/components
instead of /service/rest/v1/components
DTO generation fails with dk.mada.jaxrs:openapi-jaxrs-client:0.9.20
Cause of error is the presence of a "http response 425"
Caused by: java.lang.IllegalArgumentException: Unknown HTTP status code: '425'
at dk.mada.jaxrs.model.api.StatusCode.of(StatusCode.java:136)
If you remove the http 425 it will parse the yaml without errors
`responses:
200:
description: Validerer at kro sagen er klar til dannelse af beslutningsgrundlaget
content:
application/json:
schema:
$ref: '#/components/schemas/klarTilBeslutningsGrundlagResponse'
409:
$ref: '#/components/responses/409'
425:
description: >-
Indikerer at sagen endnu ikke er klar til beslutning.
Se fejlmeddelse for detaljer.
content:
application/json:
schema:
$ref: '#/components/schemas/errorMessage'
400:
$ref: '#/components/responses/400'
401:
$ref: '#/components/responses/401'`
Another issue is parsing timestamp definitions that uses ref
It will be generated as
private <UNKNOWN> senestOpdat;
Which wont compile. It should propably have been type String instead of UNKNOWN
klarTilBeslutningsGrundlagResponse:
type: object
required:
- senestOpdat
properties:
senestOpdat:
$ref: '#/components/schemas/senest-opdat'
senest-opdat:
$ref: '#/components/schemas/bdws-timestamp'
bdws-timestamp:
type: string
example: "2022-02-18-09.18.12.788990"
pattern: '^(\d{4}-\d{2}-\d{2}-\d{2}.\d{2}.\d{2}.\d{6})$'
Today the option generator-use-empty-collections
replaced the normal getters with variants that translate null to empty collections.
And add a companion getter with a different name, that allows getting the raw property value (null or otherwise).
This causes problems when serializing (double properties).
The ideal code would be something like this:
@Valid
public List getFoobar() {}
@Valid
@JsonIgnore
public List getFoobarReplacingNullWithEmptyCollection() {}
But we have a lot of existing code expecting the default getter to do the null-for-collection magic.
Maybe add option to make naming the correct way.
And play with ignore-annotations to see if the existing code generation can be made to work (even if simple getter is ignored, the setter causes the field to be exposes double).
Default to on for both, but allow suppression of API code generation
Allow Enum deserializer to declare an UNKNOWN value that all unknown strings will be translated to.
This should maybe be controllable per-type/matcher?
UNKNOWN should not be hardwired, but something that can be specified.
To replace the existing generated code which will explode with IllegalArgumentException.
See enumUnknownDefaultCase option in standard generator
(JB T158-7)
If the possible DTOs have distinct properties, use these in combination to determine which sub-type to build from the values of the combined DTO.
Assumes that the deserializer can be made for an interface.
At present, if a method is void, no @produces will be added.
But this will make the MP client (as per spec) use Accept: application/json.
Not all services are happy with this and explode with status 500.
Allow specifying a default to be used for void methods, such as "*/*".
This will allow handling any type (e.g. text error messages).
This should probably be the default (would match what curl asks for).
For symmetry, allow option to control @consumes for types having no body (although I cannot see it making a difference)
(Lynge)
Laanapi see conflict renaming:
Prioritetsoversigt.FremmedLaan FremmedLaanX
Engagement.FremmedLaan FremmedLaan
But want (option?) by OpenApi document order instead.
Maybe allow both explicit names, regexp, and file patterns.
Workaround for now:
generator-map-external-types = \
ignore: \
BarDto \
FooDto
vs keeping declaration order from OpenApi document.
@ParameterizedTest
@CsvSource({
"JRAftalegrundlagBackendServiceInterfaceContractInterfaceAftalegrundlag20200301AftalegrundlagAftalegrundlag, Aftalegrundlag"
})
void regexpExpressionTests(String input, String expected) {
Naming sut = makeSut("REGEXP/JRAftalegrundlagBackendServiceInterfaceContractInterfaceAftalegrundlag20200301Aftalegrundlag/_/;");
assertThat(sut.convertTypeName(input))
.isEqualTo(expected);
}
The above does replace with _. But cannot replace with // (empty)
(Per)
If server declares multiple multi-media types in consume/produces, the MP rest-client will select the first (valid) from the list.
But the developer may want to override with a different, specific choice.
Allow a preferred media-type (for both consumes/produces) which is the only one rendered in the API class.
See discussion in eclipse/microprofile-rest-client#110
bpm motor contains at least one type with fields that have no declared type.
Swagger editor treats these as (plain) Object.
Add option to allow same behavior - but disabled by default.
Fail in parsing if this is seen and the option is disabled.
AendringElement:
required:
- aendring
type: object
properties:
aendring:
description: Ændring som beskrevet i KRO API
type: string
oprindelig:
description: Oprindelig værdi
ny:
description: Ønsket værdi
valueType:
description: Værdiens tekniske type (JAVA)
type: string
ContactV2MergePatch:
properties:
email:
description: Email address of the contact. Must comply with regex '^(?=.{1,64}@)[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)*@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(\.[A-Za-z]{2,})$'
Ends up as:
@JsonbProperty(JSON_PROPERTY_EMAIL)
@Schema(nullable = true, description = "Email address of the contact. Must comply with regex '^(?=.{1,64}@)[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)*@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(\.[A-Za-z]{2,})$'", example = "[email protected]")
private String email;
which results in: Illegal escape character
(Martinsen)
Default should be to use @javax.annotation.processing.Generated (moved/introduced in java 9)
If alloflookup is given this rename option, it fails as before:
// TODO: figure out why this breaks the lookup - it should have no effect given the input
naming-rules-type = TYPENAME; \
REGEXP/.*_//
The test input contains no _, so it is unclear why this should have any effect at all.
https://swagger.io/docs/specification/describing-parameters/
Seen with
/projects/{id}/tags/{tag}:
get:
tags:
- Projects
parameters:
- name: id
in: path
required: true
schema:
$ref: '#/components/schemas/ProjectId'
- name: tag
in: path
required: true
schema:
type: string
where tag
should really be annotated with @encoded because it can contain the reserved character /.
aftalepakke has:
"TilknytAftalegrundlagResponse": {
"type": "object",
"properties": {}
},
which with 0.9.22 is referenced as that type - but the DTO is filtered out, because it is just a PlainObject.
Need to add test and handling of this.
The ACR API contains some operation ids that are not nice in Java, see https://raw.githubusercontent.com/Azure/azure-rest-api-specs/master/specification/containerregistry/data-plane/Microsoft.ContainerRegistry/preview/2019-08-15/containerregistry.json
Maybe allow suppressing operationIds and using the synthetic input instead.
Or better, possibly, two set of naming rules - one applied when operationId is available, the other for the synthetic names.
try (Reader r = Files.newBufferedReader(configFile)) {
props.load(r);
} catch (IOException e) {
throw new UncheckedIOException("Failed to load config from " + configFile, e);
}
Should specify UTF-8 encoding.
So something like this is possible:
REGEXP/(.*prefix).(.*)/newPrefix$2/;
Also make replacements in printed strings so payload does not cause newlines.
(Thomas/Per)
Check what is now default behavior in swagger. Used to be int16 if format was missing.
If default is now int32, let mada generator use that as default as well - and add option to select int16/short.
(Lynge, krediterhverv.bpm.proces)
Currently including a description using the microprofile @Schema annotation with a description parameter changes the openapi spec from simple type ref to use allOf type ref like this
Without Schema annotation:
rolle:
$ref: '#/components/schemas/RolleBeregnetEnum'
With @Schema(description="Deltagerens rolle i koncernen. F.eks. om deltageren er debitor på koncernens engagement")
rolle:
description: Deltagerens rolle i koncernen. F.eks. om deltageren er debitor
på koncernens engagement
type: string
allOf:
- $ref: '#/components/schemas/RolleBeregnetEnum'
The last example should be a valid spec and have the same effect as the first + a property description. However this results in types being of String type rather than the refered type.
On REST API https://dac-static.atlassian.com/server/bitbucket/8.15.swagger.v3.json?_v=1.466.0
Unclear as yet which part of the API triggers the explosion.
2023-12-05T13:15:59.961+0100 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] Caused by: java.lang.NullPointerException: Cannot invoke "io.swagger.v3.oas.models.media.Schema.getType()" because "schema" is null
2023-12-05T13:15:59.961+0100 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] at dk.mada.jaxrs.openapi.TypeConverter.reference(TypeConverter.java:171)
2023-12-05T13:15:59.961+0100 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] at dk.mada.jaxrs.openapi.TypeConverter.toReference(TypeConverter.java:124)
2023-12-05T13:15:59.961+0100 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] at dk.mada.jaxrs.openapi.ApiTransformer.toParam(ApiTransformer.java:281)
2023-12-05T13:15:59.961+0100 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] at dk.mada.jaxrs.openapi.ApiTransformer.getParameters(ApiTransformer.java:269)
2023-12-05T13:15:59.961+0100 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] at dk.mada.jaxrs.openapi.ApiTransformer.processOp(ApiTransformer.java:117)
2023-12-05T13:15:59.961+0100 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] at dk.mada.jaxrs.openapi.ApiTransformer.lambda$processPath$0(ApiTransformer.java:107)
2023-12-05T13:15:59.962+0100 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] at dk.mada.jaxrs.openapi.ApiTransformer.processPath(ApiTransformer.java:107)
2023-12-05T13:15:59.962+0100 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] at dk.mada.jaxrs.openapi.ApiTransformer.transform(ApiTransformer.java:97)
2023-12-05T13:15:59.962+0100 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] at dk.mada.jaxrs.openapi.Parser.parse(Parser.java:114)
2023-12-05T13:15:59.962+0100 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] at dk.mada.jaxrs.generator.mpclient.Generator.generateClient(Generator.java:51)
2023-12-05T13:15:59.962+0100 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] ... 33 more
For example:
ProjectId:
type: string
Gets remapped to String.
But:
(Per/Jesper)
/project-id/from-clone-url:
put:
tags:
- Project Id Resource
requestBody:
content:
application/json:
schema:
type: string
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
type: string
text/plain:
schema:
type: string
Results in (invalid method name):
@javax.annotation.processing.Generated(value = "dk.mada.jaxrs.Generator")
@Path("/project-id/from-clone-url")
public interface Project_Id_ResourceApi {
/**
* project-idFromCloneUrlPut.
*
* @param dto (required)
* @return String
*/
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
@APIResponseSchema(String.class)
String project-idFromCloneUrlPut(@NotNull String dto);
}
In the below API, the RoomId is handled as a DTO whereas UUID is handled as the java UUID type.
The parser logic handling this takes its cue from OpenApi schema type (UUIDSchema) not the type name.
So it is unclear if this is a OpenApi problem, or mada parser jumping the gun somewhere else.
Check if it is because the pattern is missing (could trigger magic in OpenAPI).
/api/uuid/query:
get:
tags:
- Resource Dtos
parameters:
- in: query
name: roomId
schema:
$ref: '#/components/schemas/RoomId'
...
UUID:
format: uuid
pattern: "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"
type: string
RoomId:
description: UUID of the room.
example: e017e2ea-ff98-4516-9a56-bbd02e1f161a
format: uuid
type: string
(MM)
See https://swagger.io/specification/#responses-object
The ACR api declares a 200 response without type (schema), which causes generator to insist on parser-fixup-void-200-to-204.
It should instead use the provided default-schema.
{
"swagger": "2.0",
"info": {
"title": "Azure Container Registry",
"description": "Metadata API definition for the Azure Container Registry runtime",
"version": "2019-08-15-preview"
},
...
"paths": {
"/v2/": {
"get": {
"tags": [
"v2"
],
"description": "Tells whether this Docker Registry instance supports Docker Registry HTTP API v2",
"operationId": "V2Support_Check",
"x-ms-examples": {
"Check Docker Registry V2 Support": {
"$ref": "./examples/GetDockerRegistryV2Support.json"
}
},
"responses": {
"200": {
"description": "Successful response. API v2 supported"
},
"default": {
"description": "ACR error response describing why the operation failed.",
"schema": {
"$ref": "#/definitions/AcrErrors"
}
}
}
}
},
Add option to deal with properties of allof-subtypes that share name, but not validation, examples, or description
This example (customerRoomV3) renders Identification
as abstract, but the three subtypes does not extend it.
PrivateIdentification:
allOf:
- $ref: '#/components/schemas/Identification'
- properties:
sevenFirstCprDigits:
type: string
type: object
VirkPersonIdentification:
allOf:
- $ref: '#/components/schemas/Identification'
- properties:
virkUnitNumber:
type: string
type: object
SMSMailIdentification:
allOf:
- $ref: '#/components/schemas/Identification'
- properties:
countryCode:
type: string
type: object
IdentificationType:
enum:
- PRIVATE
- VIRK_PERSON
- SMS_MAIL
type: string
Identification:
required:
- type
properties:
type:
$ref: '#/components/schemas/IdentificationType'
locked:
default: false
readOnly: true
type: boolean
loginId:
readOnly: true
type: string
discriminator:
mapping:
PRIVATE: '#/components/schemas/PrivateIdentification'
VIRK_PERSON: '#/components/schemas/VirkPersonIdentification'
SMS_MAIL: '#/components/schemas/SMSMailIdentification'
propertyName: type
nullable: true
type: object
(MM)
(aftalepakke) has endpoint with these:
"consumes":["application/json","text/json","application/xml","text/xml","application/x-www-form-urlencoded"],"produces":["application/json","text/json","application/xml","text/xml"]
which ends up getting rendered (badly) as:
@Consumes(MediaType.APPLICATION_JSON)
@Produces({"application/xml", "text/json", "text/xml", MediaType.APPLICATION_JSON})
@APIResponses({
@APIResponse(responseCode = "200", description = "OK",
content = @Content(schema = @Schema(implementation = DTOtype.class), mediaType = {"application/xml", "text/json", "text/xml", MediaType.APPLICATION_JSON})),
Should just be:
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@APIResponses({
@APIResponse(responseCode = "200", description = "OK",
content = @Content(schema = @Schema(implementation = DTOtype.class), mediaType = MediaType.APPLICATION_JSON)),
(and most definitely not a list)
That is with preferences:
parser-api-preferred-request-mediatypes = application/json
parser-api-preferred-response-mediatypes = application/json
Not sure why it fails sometimes. Works mostly.
Need to explore on JB network, see Outlook/Parkering/openapi:generateClient.... (from Per)
They contain no spec-related information anyway, so they end up being randomly ordered (parse order).
eclipse/microprofile-open-api#359
also, from the JSON schema spec https://datatracker.ietf.org/doc/html/draft-wright-json-schema-00#section-4.3 :
Implied in this definition is that arrays must be the same length,
objects must have the same number of members, properties in objects
are unordered, there is no way to define multiple properties with the
same key, and mere formatting differences (indentation, placement of
commas, trailing zeros) are insignificant.
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.