diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c379f1..10d5fff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,9 @@ jobs: test: name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} runs-on: ${{ matrix.os }} + permissions: + contents: read + id-token: write strategy: fail-fast: false matrix: @@ -24,12 +27,12 @@ jobs: version: '1' arch: x86 steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v1 + - uses: actions/cache@v4 env: cache-name: cache-artifacts with: @@ -42,16 +45,24 @@ jobs: - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v5 with: - file: lcov.info + files: lcov.info + # Repository jobs authenticate with GitHub OIDC; public fork PRs upload without a token. + use_oidc: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} docs: name: Documentation runs-on: ubuntu-latest + permissions: + contents: write + statuses: write steps: - - uses: actions/checkout@v2 - - uses: julia-actions/julia-buildpkg@latest - - uses: julia-actions/julia-docdeploy@latest + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 + with: + version: '1' + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-docdeploy@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} diff --git a/docs/Project.toml b/docs/Project.toml index dfa65cd..c2b4a8e 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,2 +1,6 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" + +[compat] +Documenter = "1" diff --git a/docs/make.jl b/docs/make.jl index 98b5fac..5a704bd 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -6,10 +6,9 @@ makedocs(; pages=[ "Home" => "index.md", ], - repo="https://github.com/JuliaData/StructTypes.jl/blob/{commit}{path}#L{line}", + repo=Documenter.Remotes.GitHub("JuliaData", "StructTypes.jl"), sitename="StructTypes.jl", authors="Jacob Quinn", - assets=String[], ) deploydocs(; diff --git a/docs/src/index.md b/docs/src/index.md index 2eddafd..29cc731 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -14,6 +14,10 @@ Depth = 3 ## `StructTypes.StructType` +```@docs +StructTypes.StructType +``` + In general, custom Julia types tend to be one of: 1) "data types", 2) "interface types" or sometimes 3) "custom types" or 4) "abstract types" with a known set of concrete subtypes. Data types tend to be "collection of fields" kind of types; fields are generally public and directly accessible, they might also be made to model "objects" in the object-oriented sense. In any case, the type is "nominal" in the sense that it's "made up" of the fields it has, sometimes even if just for making it more convenient to pass them around together in functions. Interface types, on the other hand, are characterized by **private** fields; they contain optimized representations "under the hood" to provide various features/functionality and are useful via interface methods implemented: iteration, `getindex`, accessor methods, etc. Many package-provided libraries or Base-provided structures are like this: `Dict`, `Array`, `Socket`, etc. For these types, their underlying fields are mostly cryptic and provide little value to users directly, and are often explictly documented as being implementation details and not to be relied upon directly under warning of breakage. @@ -33,6 +37,10 @@ Each of these 4 kinds of struct type categories will be now be detailed. ### DataTypes +```@docs +StructTypes.DataType +``` + You'll remember that "data types" are Julia structs that are "made up" of their fields. In the object-oriented world, this would be characterized by marking a field as `public`. A quick example is: @@ -74,6 +82,7 @@ Support functions for `StructTypes.DataType`s: StructTypes.names StructTypes.excludes StructTypes.omitempties +StructTypes.defaults StructTypes.keywordargs StructTypes.idproperty StructTypes.fieldprefix @@ -81,6 +90,10 @@ StructTypes.fieldprefix ### Interface Types +```@docs +StructTypes.InterfaceType +``` + For interface types, we don't want the internal fields of a type exposed, so an alternative API is to define the closest "basic" type that our custom type should map to. This is done by choosing one of the following definitions: ```julia StructTypes.StructType(::Type{MyType}) = StructTypes.DictType() @@ -129,6 +142,12 @@ StructTypes.BoolType StructTypes.NullType ``` +#### `StructTypes.NoStructType` + +```@docs +StructTypes.NoStructType +``` + ### CustomStruct ```@docs @@ -165,14 +184,18 @@ The `StructType` of a type can be set using the following utility macros StructTypes.@Struct ``` -And similarly for -- `StructTypes.@Mutable` -- `StructTypes.@CustomStruct` -- `StructTypes.@OrderedStruct` -- `StructTypes.@AbstractType` -- `StructTypes.@DictType` -- `StructTypes.@ArrayType` -- `StructTypes.@StringType` -- `StructTypes.@NumberType` -- `StructTypes.@BoolType` -- `StructTypes.@NullType` \ No newline at end of file +The corresponding macros are available for the other struct types: + +```@docs +StructTypes.@Mutable +StructTypes.@CustomStruct +StructTypes.@OrderedStruct +StructTypes.@AbstractType +StructTypes.@DictType +StructTypes.@ArrayType +StructTypes.@StringType +StructTypes.@NumberType +StructTypes.@BoolType +StructTypes.@NullType +StructTypes.@register_struct_subtype +``` diff --git a/src/StructTypes.jl b/src/StructTypes.jl index 2a45d86..8baf61d 100644 --- a/src/StructTypes.jl +++ b/src/StructTypes.jl @@ -34,7 +34,7 @@ struct CustomStruct <: StructType end This is a required method for types declaring `StructTypes.CustomStruct`. Allows objects of type `T` to conveniently serialize/deserialize as another type, when their own structure/definition isn't significant. Useful for wrapper types. See also -[`StructTypes.CustomStruct`](@ref) and [`StructType.lowertype`](@ref). +[`StructTypes.CustomStruct`](@ref) and [`StructTypes.lowertype`](@ref). """ function lower end @@ -218,7 +218,7 @@ isempty(::Type{T}, x) where {T} = isempty(x) # generic fallback isempty(::Type{T}, x, i) where {T} = isempty(T, Core.getfield(x, i)) # generic fallback """ - StructTypes.defaults(::Type{MyType}) = (:field_a=default_value, :field_b=>default_value) + StructTypes.defaults(::Type{MyType}) = (field_a=default_value, field_b=default_value) Define default arguments for various fields of `MyType`, which will be used to initialize the name-value dictionary used in `StructTypes.construct`. @@ -847,11 +847,11 @@ end """ StructTypes.applyfield(f, ::Type{T}, nm::Symbol) => Bool -Convenience function for working with a `StructTypes.Mutable` object. For a given serialization name `nm`, +Convenience function for working with the fields of type `T`. For a given serialization name `nm`, apply the function `f(i, name, FT; kw...)` to the field index `i`, field name `name`, field type `FT`, and any keyword arguments `kw` defined in `StructTypes.keywordargs`. Various StructType configurations are respected like keyword arguments, names, and exclusions. -`applyfield` returns whether `f` was executed or not; if `nm` isn't a valid field name on `x`, `false` +`applyfield` returns whether `f` was executed or not; if `nm` isn't a valid field name on `T`, `false` will be returned (important for applications where the input still needs to consume the field, like json parsing). Note that the input `nm` is treated as the serialization name, so any `StructTypes.names` mappings will be applied, and the function will be passed the Julia field name. @@ -890,9 +890,6 @@ mappings will be applied, and the function will be passed the Julia field name. end end ) - if !f_applied && haskey(defaults(T), nm) - setfield!(x, nm, defaults(T)[nm]) - end return f_applied end diff --git a/test/runtests.jl b/test/runtests.jl index 1ca2e57..8712567 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -72,6 +72,7 @@ builtin_type_mapping = Dict( Base.Cwstring, Base.Regex, Base.VersionNumber, + UUID, }, StructTypes.NumberType() => Union{ Core.Number, # Should all of `Number` and its subtypes be Numbers by default? @@ -825,6 +826,22 @@ StructTypes.defaults(::Type{Defaultable}) = (b="b",) @test StructTypes.constructfrom(Defaultable, (a="a", b="c")) == Defaultable("a", "c") end +struct DefaultedFields + value::Int +end +StructTypes.StructType(::Type{DefaultedFields}) = StructTypes.Struct() +StructTypes.defaults(::Type{DefaultedFields}) = (value=10, extra=20) + +@testset "applyfield with defaults for unknown names" begin + calls = Ref(0) + callback = (i, nm, FT) -> (calls[] += 1; 99) + @test StructTypes.applyfield(callback, DefaultedFields, :extra) === false + @test calls[] == 0 + @test StructTypes.applyfield(callback, DefaultedFields, :value) === true + @test calls[] == 1 + @test StructTypes.constructfrom(DefaultedFields, Dict{Symbol, Int}()).value == 10 +end + # CustomStruct subtypes abstract type Vehicle2 end